lutaml-model 0.8.91 → 0.8.92

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: f05bb7edf0d4a86ba5bc0105d3d269b2f3c28532cdd88e03be857fb4e426a505
4
- data.tar.gz: 806a73d21e506337a5d039563225e3b000e7f1a3c0e701ca58ae8d58fdbe3c8d
3
+ metadata.gz: 19d1cc9a53554486e1dda21b67751062851d1990b9878aac1dcd6ccd2542d28e
4
+ data.tar.gz: 459cecd7cbdd127bb65a52cd6058d027e5139f2926e46d408bea5a28b13f2e7f
5
5
  SHA512:
6
- metadata.gz: 0d02bf01cd956193aeab21358508339542b2f104c767347dd7fd2fbc7134c461692d4358f755e952082570683e7ecaf0e39c989d977d5b458b87807892b24997
7
- data.tar.gz: e833ac9280da9e33ebb3427f6d34d53f3da5dd5f7d43d408144cefc4c5087507cc92013b956d1d7b857a4d4e5bde119e58ea926195f07d95eba4ceef4e73a704
6
+ metadata.gz: 92637d4ab1efbd63d746983b4e359f2d49c00469e94bb249a050f36f1b80f15b18ae705245fdd8ab563d854e814fea54ce99993f64dbc17beb9c87555f7b3a95
7
+ data.tar.gz: 54412f06f6f1bed27f74a85495b414cfc6da3274ab03214220c27edd344aff8cd58f70874fa317fb61b9e8523a04725d69152dfde490b3aa61a2d75540a161ed
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.91"
5
+ VERSION = "0.8.92"
6
6
  end
7
7
  end
@@ -1197,6 +1197,31 @@ module Lutaml
1197
1197
  # transform applies the collection-attribute filter.
1198
1198
  #
1199
1199
  # @return [Hash{Symbol, String => Array<MappingRule>}]
1200
+ # The when_attribute siblings-vs-plain-names partition of the
1201
+ # element rules. A ParseSession used to rebuild this per parsed
1202
+ # node (sessions are per-element), paying an O(rules) pass on
1203
+ # every node of large documents; it is a pure function of the
1204
+ # rules, so it memoizes here — same lifetime as
1205
+ # plain_element_rules_by_attr.
1206
+ def when_attribute_partition
1207
+ @when_attribute_partition ||= begin
1208
+ siblings = {}
1209
+ plain_names = {}
1210
+ mappings.each do |rule|
1211
+ next if rule.attribute? || rule.content_mapping? ||
1212
+ rule.raw_mapping? || rule.cdata
1213
+
1214
+ pairs = rule.when_attribute
1215
+ if pairs && !pairs.empty?
1216
+ (siblings[rule.name.to_s] ||= []) << rule
1217
+ else
1218
+ plain_names[rule.name.to_s] = true
1219
+ end
1220
+ end
1221
+ [siblings, plain_names]
1222
+ end
1223
+ end
1224
+
1200
1225
  def plain_element_rules_by_attr(register_id = nil)
1201
1226
  reg_key = register_id || :default
1202
1227
  @plain_element_rules_by_attr ||= {}
@@ -344,6 +344,14 @@ module Lutaml
344
344
  deserialize_attributes = attributes
345
345
  mappings.each do |rule|
346
346
  record = records&.[](rule)
347
+
348
+ # #903: replay-or-drop unmatched named element rules before
349
+ # any per-rule machinery (see replay_unmatched_rule).
350
+ if replay_unmatched_rule(child_names_set, record, rule,
351
+ instance, defaults_used, xml_mapping)
352
+ next
353
+ end
354
+
347
355
  rule_to = rule.to
348
356
 
349
357
  if record
@@ -847,7 +855,9 @@ _effective_register)
847
855
  Lutaml::Xml::HtmlEntities.decode(value)
848
856
  end
849
857
 
850
- Record = Struct.new(:attr, :derived, :group_skip, :valid)
858
+ Record = Struct.new(:attr, :derived, :group_skip, :valid,
859
+ :unmatched_mode, :replay_writer,
860
+ :unmatched_names)
851
861
 
852
862
  def compiled_rule_records(model_class, register, grouped_plain_rules)
853
863
  return {}.compare_by_identity unless model_class.include?(Lutaml::Model::Serialize)
@@ -866,12 +876,76 @@ _effective_register)
866
876
  mapping&.mappings(register)&.each do |rule| # rubocop:disable Style/SafeNavigationChain,Style/SafeNavigation
867
877
  attr = attribute_for_rule_static(model_class, rule, register)
868
878
  group = grouped_plain_rules[rule.to]
879
+ # #903: for a plain rule, what an unmatched visit writes is
880
+ # known statically. The fast-skip branch hands the attribute's
881
+ # uninitialized sentinel to the pipeline; with no value-map
882
+ # overrides and no transformers every step is identity except
883
+ # apply_value_map, which maps the :omitted key: the default
884
+ # map (:nil) yields nil and the visit writes nil through the
885
+ # setter; an explicit omitted: :omitted map treats it as
886
+ # treat_omitted? == false and the omission gate ends the
887
+ # visit untouched. Both endings replay without the walk:
888
+ # :write_nil (setter write + flag churn) or :drop (nothing).
889
+ # Everything else -- defaults, aliases, custom from:,
890
+ # delegates, when_attribute dispatch, transformers, value
891
+ # maps beyond those two -- keeps its visit.
892
+ unmatched_mode = nil
893
+ replay_writer = nil
894
+ if attr && !attr.derived? &&
895
+ !rule.render_default && !rule.multiple_mappings? &&
896
+ !rule.has_custom_method_for_deserialization? &&
897
+ !rule.delegate &&
898
+ !(rule.when_attribute && !rule.when_attribute.empty?)
899
+ rule_transform = rule.transform
900
+ attr_transform = attr.transform
901
+ no_transforms =
902
+ (rule_transform.nil? ||
903
+ (rule_transform.respond_to?(:empty?) &&
904
+ rule_transform.empty?)) &&
905
+ (attr_transform.nil? ||
906
+ (attr_transform.respond_to?(:empty?) &&
907
+ attr_transform.empty?))
908
+ rule_default = attr.default(register)
909
+ if no_transforms &&
910
+ (rule_default.nil? ||
911
+ rule_default.is_a?(::Lutaml::Model::UninitializedClass)) &&
912
+ rule.class.transform_dispatch(rule, attr) == :assign
913
+ if group && group.size > 1 && attr.collection? &&
914
+ !group.first.equal?(rule)
915
+ # Non-first spelling of a #765 group: the visit is a
916
+ # guarded next (the first member does the merged match).
917
+ unmatched_mode = :drop
918
+ else
919
+ omitted_val = rule.value_map(:from)[:omitted]
920
+ if omitted_val == :nil
921
+ unmatched_mode = :write_nil
922
+ elsif omitted_val == :omitted
923
+ unmatched_mode = :drop
924
+ end
925
+ end
926
+ end
927
+ end
928
+ # A first group member does the merged match for every
929
+ # spelling, so its name-miss test spans the whole group.
930
+ unmatched_names = if group && group.size > 1
931
+ group.map { |r| r.name.to_s }
932
+ else
933
+ [rule.name.to_s]
934
+ end
935
+ if unmatched_mode == :write_nil
936
+ replay_writer =
937
+ rule.class.parsed_assign_writer(model_class, rule.to)
938
+ unmatched_mode = nil unless replay_writer
939
+ end
869
940
  records[rule] = Record.new(
870
941
  attr,
871
942
  attr&.derived? || false,
872
943
  !!(group && group.size > 1 && attr&.collection? &&
873
944
  !group.first.equal?(rule)),
874
945
  !!(attr || rule.custom_methods[:from]),
946
+ unmatched_mode,
947
+ replay_writer,
948
+ unmatched_names,
875
949
  )
876
950
  end
877
951
  entry = Struct.new(:version, :records).new(version, records)
@@ -1551,6 +1625,34 @@ effective_register = lutaml_register)
1551
1625
  # @param child_names_set [Set] Set of child namespaced_name and unprefixed_name
1552
1626
  # @param default_namespace [String, nil] the default namespace
1553
1627
  # @return [Boolean] true if a matching child likely exists
1628
+ # #903: for an unmatched named element rule whose record classed
1629
+ # it, replay what the visit would write without the walk: :drop
1630
+ # (the visit ended at the omission gate or was a guarded next)
1631
+ # writes nothing; :write_nil replays the nil the default value
1632
+ # map produces, plus the flag churn. Returns true when handled.
1633
+ def replay_unmatched_rule(child_names_set, record, rule, instance,
1634
+ defaults_used, xml_mapping)
1635
+ return false unless child_names_set && record
1636
+
1637
+ mode = record.unmatched_mode
1638
+ return false unless mode
1639
+ return false if rule.attribute? || rule.content_mapping? ||
1640
+ rule.raw_mapping? || !rule.name ||
1641
+ xml_mapping.any_element_rule.equal?(rule)
1642
+
1643
+ matched = record.unmatched_names.any? do |n|
1644
+ child_names_set.include?(n)
1645
+ end
1646
+ return false if matched
1647
+
1648
+ return true if mode == :drop
1649
+
1650
+ defaults_used << rule.to
1651
+ instance.public_send(record.replay_writer, nil)
1652
+ instance.value_set_for(rule.to)
1653
+ true
1654
+ end
1655
+
1554
1656
  def child_matches_rule?(rule, child_names_set, default_namespace)
1555
1657
  rule_names = rule.namespaced_names(default_namespace)
1556
1658
  # Array#intersect? requires an Array argument; child_names_set may
@@ -84,25 +84,9 @@ module Lutaml
84
84
  # Empty unless the mapping uses when_attribute at all; non-Serialize
85
85
  # custom models have no mappings to partition.
86
86
  def when_attribute_partition
87
- @when_attribute_partition ||=
88
- if instance_is_serialize && (mapping = xml_mapping)
89
- siblings = {}
90
- plain_names = {}
91
- mapping.mappings.each do |rule|
92
- next if rule.attribute? || rule.content_mapping? ||
93
- rule.raw_mapping? || rule.cdata
94
-
95
- pairs = rule.when_attribute
96
- if pairs && !pairs.empty?
97
- (siblings[rule.name.to_s] ||= []) << rule
98
- else
99
- plain_names[rule.name.to_s] = true
100
- end
101
- end
102
- [siblings, plain_names]
103
- else
104
- [{}, {}]
105
- end
87
+ return [{}, {}] unless instance_is_serialize && (mapping = xml_mapping)
88
+
89
+ mapping.when_attribute_partition
106
90
  end
107
91
 
108
92
  def when_attribute_siblings_by_name
data/lutaml-model.gemspec CHANGED
@@ -43,7 +43,7 @@ Gem::Specification.new do |spec|
43
43
  # input-buffer cap (lutaml-model#871).
44
44
  spec.add_dependency "moxml", "~> 0.5.84"
45
45
  spec.add_dependency "ostruct"
46
- spec.add_dependency "rubyzip", "~> 3.4"
46
+ spec.add_dependency "rubyzip", "~> 3.7"
47
47
  spec.add_dependency "thor"
48
48
 
49
49
  spec.metadata["rubygems_mfa_required"] = "true"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.91
4
+ version: 0.8.92
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -120,14 +120,14 @@ dependencies:
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '3.4'
123
+ version: '3.7'
124
124
  type: :runtime
125
125
  prerelease: false
126
126
  version_requirements: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '3.4'
130
+ version: '3.7'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: thor
133
133
  requirement: !ruby/object:Gem::Requirement