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 +4 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/mapping.rb +25 -0
- data/lib/lutaml/xml/model_transform.rb +103 -1
- data/lib/lutaml/xml/parse_session.rb +3 -19
- data/lutaml-model.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19d1cc9a53554486e1dda21b67751062851d1990b9878aac1dcd6ccd2542d28e
|
|
4
|
+
data.tar.gz: 459cecd7cbdd127bb65a52cd6058d027e5139f2926e46d408bea5a28b13f2e7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92637d4ab1efbd63d746983b4e359f2d49c00469e94bb249a050f36f1b80f15b18ae705245fdd8ab563d854e814fea54ce99993f64dbc17beb9c87555f7b3a95
|
|
7
|
+
data.tar.gz: 54412f06f6f1bed27f74a85495b414cfc6da3274ab03214220c27edd344aff8cd58f70874fa317fb61b9e8523a04725d69152dfde490b3aa61a2d75540a161ed
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
130
|
+
version: '3.7'
|
|
131
131
|
- !ruby/object:Gem::Dependency
|
|
132
132
|
name: thor
|
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|