lutaml-model 0.3.11 → 0.3.15

Sign up to get free protection for your applications and to get access to all the features.
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