lutaml-model 0.3.11 → 0.3.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91ff48fd66cf1435e5e18508b24cc24f7cc92dc8b5d1619c7796932eb21dcea2
4
- data.tar.gz: 51eabdb2aede716ce86cea46325cad0b7b9f0da98df3235fc3b8596fe9f96bd9
3
+ metadata.gz: 59ac2ffbc292a5e34dbbbf79441a8220a48954ba5acd21d2780235281bfbbee2
4
+ data.tar.gz: 253892694acb1359120e6401d83e05bc2daa2f371b69f667210c048c08f2336c
5
5
  SHA512:
6
- metadata.gz: 3ce25727c43d1c25f97e49c529c9ded5f51d8f2c13ae2766dba8c94d2c1d9a7c646bad564bfb9cd4c5555ee71395f6ff39023268e9e92044f10985c20dd826ca
7
- data.tar.gz: edf1d699e944a2427b947e1a262258e93623c44bc765a5f5497c13bd4d806984722ececd6318254c15196d435b2b809f44389a1be1d9cd5f33e005169b232eaf
6
+ metadata.gz: 920de33ce2cc20336fa2ed87c3a2d5743f4016c8a400a881c91e4eafee0452e3fdc110429f3e71b7216f84694e546b26848c50323bc25397d32c8a1aa2e6901e
7
+ data.tar.gz: 3685a35624c0c355ee457241a308bf9e46c37c9a7b14994948a897c9210a13229c2a2832d8f595b2d83a4bf96660f1ea2b9843c83ea8ba9a73978ef67e864818
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-09-23 15:00:23 UTC using RuboCop version 1.65.1.
3
+ # on 2024-10-23 12:15:48 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: 100
9
+ # Offense count: 139
10
10
  # This cop supports safe autocorrection (--autocorrect).
11
11
  # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
12
12
  # URISchemes: http, https
@@ -30,7 +30,7 @@ Lint/DuplicateMethods:
30
30
  Exclude:
31
31
  - 'lib/lutaml/model/attribute.rb'
32
32
 
33
- # Offense count: 34
33
+ # Offense count: 37
34
34
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
35
35
  Metrics/AbcSize:
36
36
  Exclude:
@@ -44,14 +44,15 @@ Metrics/AbcSize:
44
44
  - 'lib/lutaml/model/xml_adapter/nokogiri_adapter.rb'
45
45
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
46
46
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
47
+ - 'lib/lutaml/model/xml_mapping_rule.rb'
47
48
 
48
- # Offense count: 4
49
+ # Offense count: 6
49
50
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
50
51
  # AllowedMethods: refine
51
52
  Metrics/BlockLength:
52
- Max: 42
53
+ Max: 47
53
54
 
54
- # Offense count: 26
55
+ # Offense count: 27
55
56
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
56
57
  Metrics/CyclomaticComplexity:
57
58
  Exclude:
@@ -63,7 +64,7 @@ Metrics/CyclomaticComplexity:
63
64
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
64
65
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
65
66
 
66
- # Offense count: 45
67
+ # Offense count: 50
67
68
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
68
69
  Metrics/MethodLength:
69
70
  Max: 51
@@ -71,7 +72,7 @@ Metrics/MethodLength:
71
72
  # Offense count: 4
72
73
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
73
74
  Metrics/ParameterLists:
74
- Max: 10
75
+ Max: 13
75
76
 
76
77
  # Offense count: 22
77
78
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
@@ -84,7 +85,7 @@ Metrics/PerceivedComplexity:
84
85
  - 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
85
86
  - 'lib/lutaml/model/xml_adapter/xml_document.rb'
86
87
 
87
- # Offense count: 7
88
+ # Offense count: 8
88
89
  # Configuration parameters: Prefixes, AllowedPatterns.
89
90
  # Prefixes: when, with, without
90
91
  RSpec/ContextWording:
@@ -93,8 +94,9 @@ RSpec/ContextWording:
93
94
  - 'spec/lutaml/model/xml_adapter/oga_adapter_spec.rb'
94
95
  - 'spec/lutaml/model/xml_adapter/ox_adapter_spec.rb'
95
96
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
97
+ - 'spec/lutaml/model/xml_mapping_spec.rb'
96
98
 
97
- # Offense count: 105
99
+ # Offense count: 109
98
100
  # Configuration parameters: CountAsOne.
99
101
  RSpec/ExampleLength:
100
102
  Max: 54
@@ -123,7 +125,7 @@ RSpec/MultipleDescribes:
123
125
  - 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
124
126
  - 'spec/lutaml/model/xml_adapter_spec.rb'
125
127
 
126
- # Offense count: 98
128
+ # Offense count: 119
127
129
  RSpec/MultipleExpectations:
128
130
  Max: 14
129
131
 
data/README.adoc CHANGED
@@ -423,6 +423,51 @@ end
423
423
  ----
424
424
  ====
425
425
 
426
+ === Attribute as raw string
427
+
428
+ An attribute can be set to read the value as raw string for XML, by using the `raw: true` option.
429
+
430
+ Syntax:
431
+
432
+ [source,ruby]
433
+ ----
434
+ attribute :name_of_attribute, :string, raw: true
435
+ ----
436
+
437
+ .Using the `raw` option to read raw value for an XML attribute
438
+ [example]
439
+ ====
440
+ [source,ruby]
441
+ ----
442
+ class Person < Lutaml::Model::Serializable
443
+ attribute :name, :string
444
+ attribute :description, :string, raw: true
445
+ end
446
+ ----
447
+
448
+ For the following xml
449
+ [source,xml]
450
+ ----
451
+ <Person>
452
+ <name>John Doe</name>
453
+ <description>
454
+ A <b>fictional person</b> commonly used as a <i>placeholder name</i>.
455
+ </description>
456
+ </Person>
457
+ ----
458
+
459
+ [source,ruby]
460
+ ----
461
+ > Person.from_xml(xml)
462
+ > # <Person:0x0000000107a3ca70
463
+ @description="\n A <b>fictional person</b> commonly used as a <i>placeholder name</i>.\n ",
464
+ @element_order=["text", "name", "text", "description", "text"],
465
+ @name="John Doe",
466
+ @ordered=nil,
467
+ @validate_on_set=false>
468
+ ----
469
+ ====
470
+
426
471
  == Serialization model mappings
427
472
 
428
473
  === General
@@ -612,7 +657,7 @@ end
612
657
 
613
658
  [source,xml]
614
659
  ----
615
- <example value=12><name>John Doe</name></example>
660
+ <example value="12"><name>John Doe</name></example>
616
661
  ----
617
662
 
618
663
  [source,ruby]
@@ -622,6 +667,37 @@ end
622
667
  > Example.new(value: 12).to_xml
623
668
  > #<example value="12"></example>
624
669
  ----
670
+
671
+ The map_attribute method does not inherit the root element's namespace. If you need to specify a namespace for an attribute,
672
+ you must explicitly declare the namespace and prefix in the map_attribute method.
673
+ [example]
674
+ ====
675
+ The following class will parse the XML snippet below:
676
+
677
+ [source,ruby]
678
+ ----
679
+ class Attribute < Lutaml::Model::Serializable
680
+ attribute :value, :integer
681
+
682
+ xml do
683
+ root 'example'
684
+ map_attribute 'value', to: :value, namespace: "http://www.tech.co/XMI", prefix: "xl"
685
+ end
686
+ end
687
+ ----
688
+
689
+ [source,xml]
690
+ ----
691
+ <example xl:value="20" xmlns:xl="http://www.tech.co/XMI"></example>
692
+ ----
693
+
694
+ [source,ruby]
695
+ ----
696
+ > Attribute.from_xml(xml)
697
+ > #<Attribute:0x0000000109436db8 @value=20>
698
+ > Attribute.new(value: 20).to_xml
699
+ > #<example xmlns:xl=\"http://www.tech.co/XMI\" xl:value=\"20\"/>
700
+ ----
625
701
  ====
626
702
 
627
703
 
@@ -698,7 +774,7 @@ end
698
774
 
699
775
  [source,xml]
700
776
  ----
701
- <example value=12><name>John Doe</name> is my moniker.</example>
777
+ <example value="12"><name>John Doe</name> is my moniker.</example>
702
778
  ----
703
779
 
704
780
  [source,ruby]
@@ -7,6 +7,7 @@ module Lutaml
7
7
  @name = name
8
8
  @type = cast_type(type)
9
9
  @options = options
10
+ @raw = !!options[:raw]
10
11
 
11
12
  if collection?
12
13
  validate_collection_range
@@ -14,6 +15,10 @@ module Lutaml
14
15
  end
15
16
  end
16
17
 
18
+ def delegate
19
+ @options[:delegate]
20
+ end
21
+
17
22
  def cast_type(type)
18
23
  case type
19
24
  when Class
@@ -45,8 +50,18 @@ module Lutaml
45
50
  !collection?
46
51
  end
47
52
 
53
+ def raw?
54
+ @raw
55
+ end
56
+
57
+ def render_default?
58
+ !!@options[:render_default]
59
+ end
60
+
48
61
  def default
49
- value = if options[:default].is_a?(Proc)
62
+ value = if delegate
63
+ type.attributes[to].default
64
+ elsif options[:default].is_a?(Proc)
50
65
  options[:default].call
51
66
  else
52
67
  options[:default]
@@ -42,6 +42,16 @@ module Lutaml
42
42
  raise IncorrectMappingArgumentsError.new(msg)
43
43
  end
44
44
  end
45
+
46
+ def deep_dup
47
+ self.class.new.tap do |new_mapping|
48
+ new_mapping.instance_variable_set(:@mappings, duplicate_mappings)
49
+ end
50
+ end
51
+
52
+ def duplicate_mappings
53
+ @mappings.map(&:deep_dup)
54
+ end
45
55
  end
46
56
  end
47
57
  end
@@ -3,7 +3,37 @@ require_relative "mapping_rule"
3
3
  module Lutaml
4
4
  module Model
5
5
  class KeyValueMappingRule < MappingRule
6
- # No additional attributes or methods required for now
6
+ attr_reader :child_mappings
7
+
8
+ def initialize(
9
+ name,
10
+ to:,
11
+ render_nil: false,
12
+ with: {},
13
+ delegate: nil,
14
+ child_mappings: nil
15
+ )
16
+ super(
17
+ name,
18
+ to: to,
19
+ render_nil: render_nil,
20
+ with: with,
21
+ delegate: delegate,
22
+ )
23
+
24
+ @child_mappings = child_mappings
25
+ end
26
+
27
+ def deep_dup
28
+ self.class.new(
29
+ name.dup,
30
+ to: to.dup,
31
+ render_nil: render_nil.dup,
32
+ with: Utils.deep_dup(custom_methods),
33
+ delegate: delegate,
34
+ child_mappings: Utils.deep_dup(child_mappings),
35
+ )
36
+ end
7
37
  end
8
38
  end
9
39
  end
@@ -14,6 +14,14 @@ module Lutaml
14
14
  @item_order&.map { |key| normalize(key) } || keys
15
15
  end
16
16
 
17
+ def fetch(key)
18
+ self[key.to_s] || self[key.to_sym]
19
+ end
20
+
21
+ def key_exist?(key)
22
+ key?(key.to_s) || key?(key.to_sym)
23
+ end
24
+
17
25
  def item_order=(order)
18
26
  raise "`item order` must be an array" unless order.is_a?(Array)
19
27
 
@@ -4,43 +4,33 @@ module Lutaml
4
4
  attr_reader :name,
5
5
  :to,
6
6
  :render_nil,
7
+ :render_default,
8
+ :attribute,
7
9
  :custom_methods,
8
- :delegate,
9
- :mixed_content,
10
- :child_mappings
10
+ :delegate
11
11
 
12
12
  def initialize(
13
13
  name,
14
14
  to:,
15
15
  render_nil: false,
16
+ render_default: false,
16
17
  with: {},
17
- delegate: nil,
18
- mixed_content: false,
19
- namespace_set: false,
20
- prefix_set: false,
21
- child_mappings: nil
18
+ attribute: false,
19
+ delegate: nil
22
20
  )
23
21
  @name = name
24
22
  @to = to
25
23
  @render_nil = render_nil
24
+ @render_default = render_default
26
25
  @custom_methods = with
26
+ @attribute = attribute
27
27
  @delegate = delegate
28
- @mixed_content = mixed_content
29
- @namespace_set = namespace_set
30
- @prefix_set = prefix_set
31
- @child_mappings = child_mappings
32
28
  end
33
29
 
34
30
  alias from name
35
31
  alias render_nil? render_nil
36
-
37
- def prefixed_name
38
- if prefix
39
- "#{prefix}:#{name}"
40
- else
41
- name
42
- end
43
- end
32
+ alias render_default? render_default
33
+ alias attribute? attribute
44
34
 
45
35
  def serialize_attribute(model, element, doc)
46
36
  if custom_methods[:to]
@@ -48,13 +38,19 @@ module Lutaml
48
38
  end
49
39
  end
50
40
 
41
+ def to_value_for(model)
42
+ if delegate
43
+ model.public_send(delegate).public_send(to)
44
+ else
45
+ model.public_send(to)
46
+ end
47
+ end
48
+
51
49
  def serialize(model, parent = nil, doc = nil)
52
50
  if custom_methods[:to]
53
51
  model.send(custom_methods[:to], model, parent, doc)
54
- elsif delegate
55
- model.public_send(delegate).public_send(to)
56
52
  else
57
- model.public_send(to)
53
+ to_value_for(model)
58
54
  end
59
55
  end
60
56
 
@@ -72,16 +68,8 @@ module Lutaml
72
68
  end
73
69
  end
74
70
 
75
- def namespace_set?
76
- @namespace_set
77
- end
78
-
79
- def prefix_set?
80
- @prefix_set
81
- end
82
-
83
- def content_mapping?
84
- name.nil?
71
+ def deep_dup
72
+ raise NotImplementedError, "Subclasses must implement `deep_dup`."
85
73
  end
86
74
  end
87
75
  end
@@ -32,8 +32,8 @@ module Lutaml
32
32
  @mappings ||= {}
33
33
  @attributes ||= {}
34
34
 
35
- subclass.instance_variable_set(:@attributes, @attributes.dup)
36
- subclass.instance_variable_set(:@mappings, @mappings.dup)
35
+ subclass.instance_variable_set(:@attributes, Utils.deep_dup(@attributes))
36
+ subclass.instance_variable_set(:@mappings, Utils.deep_dup(@mappings))
37
37
  subclass.instance_variable_set(:@model, subclass)
38
38
  end
39
39
 
@@ -55,6 +55,14 @@ module Lutaml
55
55
  !!@ordered
56
56
  end
57
57
 
58
+ Utils.add_method_if_not_defined(klass, :mixed=) do |mixed|
59
+ @mixed = mixed
60
+ end
61
+
62
+ Utils.add_method_if_not_defined(klass, :mixed?) do
63
+ !!@mixed
64
+ end
65
+
58
66
  Utils.add_method_if_not_defined(klass, :element_order=) do |order|
59
67
  @element_order = order
60
68
  end
@@ -78,6 +86,7 @@ module Lutaml
78
86
  end
79
87
 
80
88
  define_method(:"#{name}=") do |value|
89
+ value_set_for(name)
81
90
  instance_variable_set(:"@#{name}", attr.cast_value(value))
82
91
  end
83
92
  end
@@ -85,7 +94,7 @@ module Lutaml
85
94
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
86
95
  define_method(format) do |&block|
87
96
  klass = format == :xml ? XmlMapping : KeyValueMapping
88
- mappings[format] = klass.new
97
+ mappings[format] ||= klass.new
89
98
  mappings[format].instance_eval(&block)
90
99
 
91
100
  if format == :xml && !mappings[format].root_element
@@ -107,7 +116,12 @@ module Lutaml
107
116
  end
108
117
  end
109
118
 
110
- apply_mappings(doc.to_h, format)
119
+ if format == :xml
120
+ doc_hash = doc.parse_element(doc.root, self, :xml)
121
+ apply_mappings(doc_hash, format)
122
+ else
123
+ apply_mappings(doc.to_h, format)
124
+ end
111
125
  end
112
126
 
113
127
  define_method(:"to_#{format}") do |instance|
@@ -280,38 +294,18 @@ module Lutaml
280
294
  attributes[rule.delegate].type.attributes[rule.to]
281
295
  end
282
296
 
297
+ def attribute_for_child(child_name, format)
298
+ mapping_rule = mappings_for(format).find_by_name(child_name)
299
+
300
+ attribute_for_rule(mapping_rule) if mapping_rule
301
+ end
302
+
283
303
  def apply_mappings(doc, format, options = {})
284
304
  instance = options[:instance] || model.new
285
305
  return instance if Utils.blank?(doc)
286
306
  return apply_xml_mapping(doc, instance, options) if format == :xml
287
307
 
288
- mappings = mappings_for(format).mappings
289
- mappings.each do |rule|
290
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
291
-
292
- attr = attribute_for_rule(rule)
293
-
294
- value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
295
- doc[rule.name] || doc[rule.name.to_sym]
296
- else
297
- attr.default
298
- end
299
-
300
- if rule.custom_methods[:from]
301
- if Utils.present?(value)
302
- value = new.send(rule.custom_methods[:from], instance, value)
303
- end
304
-
305
- next
306
- end
307
-
308
- value = apply_child_mappings(value, rule.child_mappings)
309
- value = attr.cast(value, format)
310
-
311
- rule.deserialize(instance, value, attributes, self)
312
- end
313
-
314
- instance
308
+ apply_hash_mapping(doc, instance, format, options)
315
309
  end
316
310
 
317
311
  def apply_xml_mapping(doc, instance, options = {})
@@ -326,7 +320,8 @@ module Lutaml
326
320
 
327
321
  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
328
322
  instance.element_order = doc.item_order
329
- instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content]
323
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
324
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
330
325
  end
331
326
 
332
327
  if doc["__schema_location"]
@@ -337,19 +332,58 @@ module Lutaml
337
332
  )
338
333
  end
339
334
 
335
+ defaults_used = []
336
+
340
337
  mappings.each do |rule|
341
338
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
342
339
 
343
340
  value = if rule.content_mapping?
344
341
  doc["text"]
342
+ elsif doc.key_exist?(rule.namespaced_name)
343
+ doc.fetch(rule.namespaced_name)
345
344
  else
346
- doc[rule.name.to_s] || doc[rule.name.to_sym]
345
+ defaults_used << rule.to
346
+ rule.to_value_for(instance)
347
347
  end
348
348
 
349
349
  value = normalize_xml_value(value, rule)
350
350
  rule.deserialize(instance, value, attributes, self)
351
351
  end
352
352
 
353
+ defaults_used.each do |attribute_name|
354
+ instance.using_default_for(attribute_name) if instance.respond_to?(:using_default_for)
355
+ end
356
+
357
+ instance
358
+ end
359
+
360
+ def apply_hash_mapping(doc, instance, format, _options = {})
361
+ mappings = mappings_for(format).mappings
362
+ mappings.each do |rule|
363
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
364
+
365
+ attr = attribute_for_rule(rule)
366
+
367
+ value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
368
+ doc[rule.name] || doc[rule.name.to_sym]
369
+ else
370
+ attr.default
371
+ end
372
+
373
+ if rule.custom_methods[:from]
374
+ if Utils.present?(value)
375
+ value = new.send(rule.custom_methods[:from], instance, value)
376
+ end
377
+
378
+ next
379
+ end
380
+
381
+ value = apply_child_mappings(value, rule.child_mappings)
382
+ value = attr.cast(value, format)
383
+
384
+ rule.deserialize(instance, value, attributes, self)
385
+ end
386
+
353
387
  instance
354
388
  end
355
389
 
@@ -407,9 +441,11 @@ module Lutaml
407
441
  end
408
442
 
409
443
  attr_accessor :element_order, :schema_location
444
+ attr_writer :ordered, :mixed
410
445
 
411
446
  def initialize(attrs = {})
412
447
  @validate_on_set = attrs.delete(:validate_on_set) || false
448
+ @using_default ||= {}
413
449
 
414
450
  return unless self.class.attributes
415
451
 
@@ -426,6 +462,7 @@ module Lutaml
426
462
  value = if attrs.key?(name) || attrs.key?(name.to_s)
427
463
  self.class.attr_value(attrs, name, attr)
428
464
  else
465
+ using_default_for(name)
429
466
  attr.default
430
467
  end
431
468
 
@@ -438,6 +475,18 @@ module Lutaml
438
475
  end
439
476
  end
440
477
 
478
+ def using_default_for(attribute_name)
479
+ @using_default[attribute_name] = true
480
+ end
481
+
482
+ def value_set_for(attribute_name)
483
+ @using_default[attribute_name] = false
484
+ end
485
+
486
+ def using_default?(attribute_name)
487
+ @using_default[attribute_name]
488
+ end
489
+
441
490
  def method_missing(method_name, *args)
442
491
  if method_name.to_s.end_with?("=") && self.class.attributes.key?(method_name.to_s.chomp("=").to_sym)
443
492
  define_singleton_method(method_name) do |value|
@@ -456,11 +505,11 @@ module Lutaml
456
505
  end
457
506
 
458
507
  def ordered?
459
- @ordered
508
+ !!@ordered
460
509
  end
461
510
 
462
- def ordered=(ordered)
463
- @ordered = ordered
511
+ def mixed?
512
+ !!@mixed
464
513
  end
465
514
 
466
515
  def key_exist?(hash, key)
@@ -116,6 +116,8 @@ module Lutaml
116
116
  def self.normalize_hash(hash)
117
117
  return hash["text"] if hash.keys == ["text"]
118
118
 
119
+ hash = hash.to_h if hash.is_a?(Lutaml::Model::MappingHash)
120
+
119
121
  hash.filter_map do |key, value|
120
122
  next if key == "text"
121
123
 
@@ -50,6 +50,24 @@ module Lutaml
50
50
  end
51
51
  end
52
52
 
53
+ def deep_dup(hash)
54
+ return hash if hash.nil?
55
+
56
+ new_hash = {}
57
+
58
+ hash.each do |key, value|
59
+ new_hash[key] = if value.is_a?(Hash)
60
+ deep_dup(value)
61
+ elsif value.respond_to?(:deep_dup)
62
+ value.deep_dup
63
+ else
64
+ value.dup
65
+ end
66
+ end
67
+
68
+ new_hash
69
+ end
70
+
53
71
  private
54
72
 
55
73
  def camelize_part(part)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.11"
5
+ VERSION = "0.3.15"
6
6
  end
7
7
  end
@@ -63,11 +63,9 @@ module Lutaml
63
63
  self
64
64
  end
65
65
 
66
- def method_missing(method_name, *args)
66
+ def method_missing(method_name, *args, &block)
67
67
  if block_given?
68
- xml.public_send(method_name, *args) do
69
- yield(xml)
70
- end
68
+ xml.public_send(method_name, *args, &block)
71
69
  else
72
70
  xml.public_send(method_name, *args)
73
71
  end
@@ -55,6 +55,7 @@ module Lutaml
55
55
  end
56
56
 
57
57
  index_hash = {}
58
+ content = []
58
59
 
59
60
  element.element_order.each do |name|
60
61
  index_hash[name] ||= -1
@@ -71,7 +72,11 @@ module Lutaml
71
72
  text = xml_mapping.content_mapping.serialize(element)
72
73
  text = text[curr_index] if text.is_a?(Array)
73
74
 
74
- prefixed_xml.text text
75
+ if element.mixed?
76
+ prefixed_xml.text text
77
+ else
78
+ content << text
79
+ end
75
80
  elsif !value.nil? || element_rule.render_nil?
76
81
  value = value[curr_index] if attribute_def.collection?
77
82
 
@@ -87,12 +92,14 @@ module Lutaml
87
92
  )
88
93
  end
89
94
  end
95
+
96
+ prefixed_xml.text content.join
90
97
  end
91
98
  end
92
99
  end
93
100
 
94
101
  class NokogiriElement < XmlElement
95
- def initialize(node, root_node: nil)
102
+ def initialize(node, root_node: nil, default_namespace: nil)
96
103
  if root_node
97
104
  node.namespaces.each do |prefix, name|
98
105
  namespace = XmlNamespace.new(name, prefix)
@@ -116,14 +123,15 @@ module Lutaml
116
123
  namespace_prefix: attr.namespace&.prefix,
117
124
  )
118
125
  end
119
-
126
+ default_namespace = node.namespace&.href if root_node.nil?
120
127
  super(
121
128
  node.name,
122
129
  attributes,
123
- parse_all_children(node, root_node: root_node || self),
130
+ parse_all_children(node, root_node: root_node || self, default_namespace: default_namespace),
124
131
  node.text,
125
132
  parent_document: root_node,
126
133
  namespace_prefix: node.namespace&.prefix,
134
+ default_namespace: default_namespace
127
135
  )
128
136
  end
129
137
 
@@ -138,8 +146,10 @@ module Lutaml
138
146
  if name == "text"
139
147
  builder.text(text)
140
148
  else
141
- builder.send(name, build_attributes(self)) do |xml|
142
- children.each { |child| child.to_xml(xml) }
149
+ builder.public_send(name, build_attributes(self)) do |xml|
150
+ children.each do |child|
151
+ child.to_xml(xml)
152
+ end
143
153
  end
144
154
  end
145
155
 
@@ -154,9 +164,9 @@ module Lutaml
154
164
  end
155
165
  end
156
166
 
157
- def parse_all_children(node, root_node: nil)
167
+ def parse_all_children(node, root_node: nil, default_namespace: nil)
158
168
  node.children.map do |child|
159
- NokogiriElement.new(child, root_node: root_node)
169
+ NokogiriElement.new(child, root_node: root_node, default_namespace: default_namespace)
160
170
  end
161
171
  end
162
172
 
@@ -42,6 +42,7 @@ module Lutaml
42
42
  builder.create_and_add_element(tag_name,
43
43
  attributes: attributes) do |el|
44
44
  index_hash = {}
45
+ content = []
45
46
 
46
47
  element.element_order.each do |name|
47
48
  index_hash[name] ||= -1
@@ -58,7 +59,11 @@ module Lutaml
58
59
  text = element.send(xml_mapping.content_mapping.to)
59
60
  text = text[curr_index] if text.is_a?(Array)
60
61
 
61
- el.add_text(el, text)
62
+ if element.mixed?
63
+ el.add_text(el, text)
64
+ else
65
+ content << text
66
+ end
62
67
  elsif !value.nil? || element_rule.render_nil?
63
68
  value = value[curr_index] if attribute_def.collection?
64
69
 
@@ -74,6 +79,8 @@ module Lutaml
74
79
  )
75
80
  end
76
81
  end
82
+
83
+ el.add_text(el, content.join)
77
84
  end
78
85
  end
79
86
  end
@@ -21,6 +21,16 @@ module Lutaml
21
21
  name
22
22
  end
23
23
  end
24
+
25
+ def namespaced_name
26
+ if unprefixed_name == "lang"
27
+ name
28
+ elsif namespace
29
+ "#{namespace}:#{unprefixed_name}"
30
+ else
31
+ unprefixed_name
32
+ end
33
+ end
24
34
  end
25
35
  end
26
36
  end
@@ -66,24 +66,43 @@ module Lutaml
66
66
  options[:tag_name] = rule.name
67
67
 
68
68
  options[:mapper_class] = attribute&.type if attribute
69
+ options[:namespace_set] = set_namespace?(rule)
69
70
 
70
71
  options
71
72
  end
72
73
 
73
- def parse_element(element)
74
+ def parse_element(element, klass = nil, format = nil)
74
75
  result = Lutaml::Model::MappingHash.new
75
76
  result.item_order = element.order
76
77
 
77
78
  element.children.each_with_object(result) do |child, hash|
78
- value = child.text? ? child.text : parse_element(child)
79
-
80
- hash[child.unprefixed_name] = if hash[child.unprefixed_name]
81
- [hash[child.unprefixed_name], value].flatten
79
+ attr = klass.attribute_for_child(child.name, format) if klass&.<= Serialize
80
+
81
+ value = if child.text?
82
+ child.text
83
+ elsif attr&.raw?
84
+ child.children.map do |c|
85
+ next c.text if c.text?
86
+
87
+ c.to_xml.doc.root.to_xml({})
88
+ end.join
89
+ else
90
+ parse_element(child, attr&.type || klass, format)
91
+ end
92
+
93
+ hash[child.namespaced_name] = if hash[child.namespaced_name]
94
+ [hash[child.namespaced_name], value].flatten
82
95
  else
83
96
  value
84
97
  end
85
98
  end
86
99
 
100
+ result.merge(attributes_hash(element))
101
+ end
102
+
103
+ def attributes_hash(element)
104
+ result = Lutaml::Model::MappingHash.new
105
+
87
106
  element.attributes.each_value do |attr|
88
107
  if attr.unprefixed_name == "schemaLocation"
89
108
  result["__schema_location"] = {
@@ -92,7 +111,7 @@ module Lutaml
92
111
  schema_location: attr.value,
93
112
  }
94
113
  else
95
- result[attr.unprefixed_name] = attr.value
114
+ result[attr.namespaced_name] = attr.value
96
115
  end
97
116
  end
98
117
 
@@ -124,6 +143,8 @@ module Lutaml
124
143
  return
125
144
  end
126
145
 
146
+ return if !render_element?(rule, element, value)
147
+
127
148
  if value && (attribute&.type&.<= Lutaml::Model::Serialize)
128
149
  handle_nested_elements(
129
150
  xml,
@@ -231,13 +252,31 @@ module Lutaml
231
252
  mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
232
253
  end
233
254
 
234
- def build_namespace_attributes(klass, processed = {})
255
+ def set_namespace?(rule)
256
+ rule.nil? || !rule.namespace_set? || !rule.namespace.nil?
257
+ end
258
+
259
+ def render_element?(rule, element, value)
260
+ render_default?(rule, element) && render_value?(rule, value)
261
+ end
262
+
263
+ def render_value?(rule, value)
264
+ rule.attribute? || rule.render_nil? || !value.nil?
265
+ end
266
+
267
+ def render_default?(rule, element)
268
+ !element.respond_to?(:using_default?) ||
269
+ rule.render_default? ||
270
+ !element.using_default?(rule.to)
271
+ end
272
+
273
+ def build_namespace_attributes(klass, processed = {}, options = {})
235
274
  xml_mappings = klass.mappings_for(:xml)
236
275
  attributes = klass.attributes
237
276
 
238
277
  attrs = {}
239
278
 
240
- if xml_mappings.namespace_uri
279
+ if xml_mappings.namespace_uri && set_namespace?(options[:caller_rule])
241
280
  prefixed_name = [
242
281
  "xmlns",
243
282
  xml_mappings.namespace_prefix,
@@ -262,10 +301,10 @@ module Lutaml
262
301
  next unless type
263
302
 
264
303
  if type <= Lutaml::Model::Serialize
265
- attrs = attrs.merge(build_namespace_attributes(type, processed))
304
+ attrs = attrs.merge(build_namespace_attributes(type, processed, { caller_rule: mapping_rule }))
266
305
  end
267
306
 
268
- if mapping_rule.namespace
307
+ if mapping_rule.namespace && mapping_rule.prefix && mapping_rule.name != "lang"
269
308
  attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
270
309
  end
271
310
  end
@@ -274,23 +313,33 @@ module Lutaml
274
313
  end
275
314
 
276
315
  def build_attributes(element, xml_mapping, options = {})
277
- attrs = namespace_attributes(xml_mapping)
316
+ attrs = if options.fetch(:namespace_set, true)
317
+ namespace_attributes(xml_mapping)
318
+ else
319
+ {}
320
+ end
321
+
322
+ if element.respond_to?(:schema_location) && element.schema_location
323
+ attrs.merge!(element.schema_location.to_xml_attributes)
324
+ end
278
325
 
279
326
  xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
280
327
  next if options[:except]&.include?(mapping_rule.to)
281
328
  next if mapping_rule.custom_methods[:to]
282
329
 
283
- if mapping_rule.namespace
330
+ if mapping_rule.namespace && mapping_rule.prefix && mapping_rule.name != "lang"
284
331
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
285
332
  end
286
333
 
287
- hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
334
+ if render_element?(mapping_rule, element, mapping_rule.to_value_for(element))
335
+ hash[mapping_rule.prefixed_name] = mapping_rule.to_value_for(element)
336
+ end
288
337
  end
289
338
 
290
339
  xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
291
340
  next if options[:except]&.include?(mapping_rule.to)
292
341
 
293
- if mapping_rule.namespace
342
+ if mapping_rule.namespace && mapping_rule.prefix
294
343
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
295
344
  end
296
345
  end
@@ -16,7 +16,8 @@ module Lutaml
16
16
  children = [],
17
17
  text = nil,
18
18
  parent_document: nil,
19
- namespace_prefix: nil
19
+ namespace_prefix: nil,
20
+ default_namespace: nil
20
21
  )
21
22
  @name = extract_name(name)
22
23
  @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
@@ -24,11 +25,20 @@ module Lutaml
24
25
  @children = children
25
26
  @text = text
26
27
  @parent_document = parent_document
28
+ @default_namespace = default_namespace
27
29
  end
28
30
 
29
31
  def name
30
- if namespace_prefix
31
- "#{namespace_prefix}:#{@name}"
32
+ return @name unless namespace_prefix
33
+
34
+ "#{namespace_prefix}:#{@name}"
35
+ end
36
+
37
+ def namespaced_name
38
+ if namespaces[namespace_prefix] && !text?
39
+ "#{namespaces[namespace_prefix].uri}:#{@name}"
40
+ elsif @default_namespace && !text?
41
+ "#{@default_namespace}:#{name}"
32
42
  else
33
43
  @name
34
44
  end
@@ -6,7 +6,8 @@ module Lutaml
6
6
  attr_reader :root_element,
7
7
  :namespace_uri,
8
8
  :namespace_prefix,
9
- :mixed_content
9
+ :mixed_content,
10
+ :ordered
10
11
 
11
12
  def initialize
12
13
  @elements = {}
@@ -16,10 +17,12 @@ module Lutaml
16
17
  end
17
18
 
18
19
  alias mixed_content? mixed_content
20
+ alias ordered? ordered
19
21
 
20
- def root(name, mixed: false)
22
+ def root(name, mixed: false, ordered: false)
21
23
  @root_element = name
22
24
  @mixed_content = mixed
25
+ @ordered = ordered || mixed # mixed contenet will always be ordered
23
26
  end
24
27
 
25
28
  def prefixed_root
@@ -40,6 +43,7 @@ module Lutaml
40
43
  name,
41
44
  to: nil,
42
45
  render_nil: false,
46
+ render_default: false,
43
47
  with: {},
44
48
  delegate: nil,
45
49
  namespace: (namespace_set = false
@@ -49,23 +53,27 @@ module Lutaml
49
53
  )
50
54
  validate!(name, to, with)
51
55
 
52
- @elements[name] = XmlMappingRule.new(
56
+ rule = XmlMappingRule.new(
53
57
  name,
54
58
  to: to,
55
59
  render_nil: render_nil,
60
+ render_default: render_default,
56
61
  with: with,
57
62
  delegate: delegate,
58
63
  namespace: namespace,
64
+ default_namespace: namespace_uri,
59
65
  prefix: prefix,
60
66
  namespace_set: namespace_set != false,
61
67
  prefix_set: prefix_set != false,
62
68
  )
69
+ @elements[rule.namespaced_name] = rule
63
70
  end
64
71
 
65
72
  def map_attribute(
66
73
  name,
67
74
  to: nil,
68
75
  render_nil: false,
76
+ render_default: false,
69
77
  with: {},
70
78
  delegate: nil,
71
79
  namespace: (namespace_set = false
@@ -75,17 +83,21 @@ module Lutaml
75
83
  )
76
84
  validate!(name, to, with)
77
85
 
78
- @attributes[name] = XmlMappingRule.new(
86
+ rule = XmlMappingRule.new(
79
87
  name,
80
88
  to: to,
81
89
  render_nil: render_nil,
90
+ render_default: render_default,
82
91
  with: with,
83
92
  delegate: delegate,
84
93
  namespace: namespace,
85
94
  prefix: prefix,
95
+ attribute: true,
96
+ default_namespace: namespace_uri,
86
97
  namespace_set: namespace_set != false,
87
98
  prefix_set: prefix_set != false,
88
99
  )
100
+ @attributes[rule.namespaced_name] = rule
89
101
  end
90
102
 
91
103
  # rubocop:enable Metrics/ParameterLists
@@ -93,6 +105,7 @@ module Lutaml
93
105
  def map_content(
94
106
  to: nil,
95
107
  render_nil: false,
108
+ render_default: false,
96
109
  with: {},
97
110
  delegate: nil,
98
111
  mixed: false
@@ -103,6 +116,7 @@ module Lutaml
103
116
  nil,
104
117
  to: to,
105
118
  render_nil: render_nil,
119
+ render_default: render_default,
106
120
  with: with,
107
121
  delegate: delegate,
108
122
  mixed_content: mixed,
@@ -158,6 +172,27 @@ module Lutaml
158
172
  end
159
173
  end
160
174
  end
175
+
176
+ def deep_dup
177
+ self.class.new.tap do |xml_mapping|
178
+ xml_mapping.root(@root_element.dup, mixed: @mixed_content, ordered: @ordered)
179
+ xml_mapping.namespace(@namespace_uri.dup, @namespace_prefix.dup)
180
+
181
+ xml_mapping.instance_variable_set(:@attributes, dup_mappings(@attributes))
182
+ xml_mapping.instance_variable_set(:@elements, dup_mappings(@elements))
183
+ xml_mapping.instance_variable_set(:@content_mapping, @content_mapping&.deep_dup)
184
+ end
185
+ end
186
+
187
+ def dup_mappings(mappings)
188
+ new_mappings = {}
189
+
190
+ mappings.each do |key, mapping_rule|
191
+ new_mappings[key] = mapping_rule.deep_dup
192
+ end
193
+
194
+ new_mappings
195
+ end
161
196
  end
162
197
  end
163
198
  end
@@ -3,29 +3,31 @@ require_relative "mapping_rule"
3
3
  module Lutaml
4
4
  module Model
5
5
  class XmlMappingRule < MappingRule
6
- attr_reader :namespace, :prefix
6
+ attr_reader :namespace, :prefix, :mixed_content, :default_namespace
7
7
 
8
8
  def initialize(
9
9
  name,
10
10
  to:,
11
11
  render_nil: false,
12
+ render_default: false,
12
13
  with: {},
13
14
  delegate: nil,
14
15
  namespace: nil,
15
16
  prefix: nil,
16
17
  mixed_content: false,
17
18
  namespace_set: false,
18
- prefix_set: false
19
+ prefix_set: false,
20
+ attribute: false,
21
+ default_namespace: nil
19
22
  )
20
23
  super(
21
24
  name,
22
25
  to: to,
23
26
  render_nil: render_nil,
27
+ render_default: render_default,
24
28
  with: with,
25
29
  delegate: delegate,
26
- mixed_content: mixed_content,
27
- namespace_set: namespace_set,
28
- prefix_set: prefix_set,
30
+ attribute: attribute,
29
31
  )
30
32
 
31
33
  @namespace = if namespace.to_s == "inherit"
@@ -35,6 +37,64 @@ module Lutaml
35
37
  namespace
36
38
  end
37
39
  @prefix = prefix
40
+ @mixed_content = mixed_content
41
+
42
+ @default_namespace = default_namespace
43
+
44
+ @namespace_set = namespace_set
45
+ @prefix_set = prefix_set
46
+ end
47
+
48
+ def namespace_set?
49
+ !!@namespace_set
50
+ end
51
+
52
+ def prefix_set?
53
+ !!@prefix_set
54
+ end
55
+
56
+ def content_mapping?
57
+ name.nil?
58
+ end
59
+
60
+ def mixed_content?
61
+ !!@mixed_content
62
+ end
63
+
64
+ def prefixed_name
65
+ if prefix
66
+ "#{prefix}:#{name}"
67
+ else
68
+ name
69
+ end
70
+ end
71
+
72
+ def namespaced_name
73
+ if name == "lang"
74
+ "#{prefix}:#{name}"
75
+ elsif namespace_set? || @attribute
76
+ [namespace, name].compact.join(":")
77
+ elsif default_namespace
78
+ "#{default_namespace}:#{name}"
79
+ else
80
+ name
81
+ end
82
+ end
83
+
84
+ def deep_dup
85
+ self.class.new(
86
+ name.dup,
87
+ to: to,
88
+ render_nil: render_nil,
89
+ with: Utils.deep_dup(custom_methods),
90
+ delegate: delegate,
91
+ namespace: namespace.dup,
92
+ prefix: prefix.dup,
93
+ mixed_content: mixed_content,
94
+ namespace_set: namespace_set?,
95
+ prefix_set: prefix_set?,
96
+ default_namespace: default_namespace.dup,
97
+ )
38
98
  end
39
99
  end
40
100
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
4
+ version: 0.3.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-04 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor