lutaml-model 0.8.137 → 0.8.138
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/serialize/initialization.rb +15 -3
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/leptris/plan_compiler.rb +2 -0
- data/lib/lutaml/xml/mapping.rb +38 -1
- data/lib/lutaml/xml/model_transform.rb +39 -0
- data/lib/lutaml/xml/transformation.rb +24 -0
- data/spec/lutaml/model/map_all_attributes_spec.rb +89 -0
- data/spec/lutaml/model/walk_constructor_spec.rb +48 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b04af29b0ab95619a185f91ca138ff213b771112707395df0aea9d093978aa5
|
|
4
|
+
data.tar.gz: c3830c610420cceb888b370417cf2f83a044aa00e10cb3bca7c71c3833587dc4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 205e61d1960bfff5771363582c8db6600f6f703a255b69e7684d40abc04434dc9ef786ec260518930afb4584db219acade52068975fea7ac39604cab82891d28
|
|
7
|
+
data.tar.gz: 1a19fc45f5880487573f2849203247015edd843f9c7000a6de992c70aaa439dbcc36e4741f1027b0ca441ee8358ec3ce1559e3f877f91476b80a3b661a3fecfd
|
|
@@ -376,7 +376,11 @@ module Lutaml
|
|
|
376
376
|
# through the real constructor for them, exactly as the walk
|
|
377
377
|
# did before the compiled seed existed. The seed stays the
|
|
378
378
|
# fast path for classes using the standard initializer.
|
|
379
|
-
return new if
|
|
379
|
+
return new(lutaml_register: register) if constructs_through_initialize?
|
|
380
|
+
# did before the compiled seed existed, register included. The
|
|
381
|
+
# seed stays the fast path for classes using the standard
|
|
382
|
+
# initializer.
|
|
383
|
+
return new(lutaml_register: register) if constructs_through_initialize?
|
|
380
384
|
|
|
381
385
|
instance = allocate
|
|
382
386
|
instance.init_walk_state(register_id)
|
|
@@ -388,8 +392,8 @@ module Lutaml
|
|
|
388
392
|
# optional setup, and the compiled walk seed would skip it.
|
|
389
393
|
# (The standard initializer's owner is Serialize::Builder; a
|
|
390
394
|
# class- or parent-defined initialize makes the owner the class.)
|
|
391
|
-
def
|
|
392
|
-
@
|
|
395
|
+
def constructs_through_initialize?
|
|
396
|
+
@constructs_through_initialize ||=
|
|
393
397
|
instance_method(:initialize).owner != Lutaml::Model::Serialize::Builder
|
|
394
398
|
end
|
|
395
399
|
|
|
@@ -407,6 +411,14 @@ module Lutaml
|
|
|
407
411
|
# stack limit — models without pending deferred imports skip
|
|
408
412
|
# it entirely.
|
|
409
413
|
ensure_imports!(register_id) if finalized? && deferred_imports_pending?(register_id)
|
|
414
|
+
# Custom-initialize classes (#1032's twin): their initialize is
|
|
415
|
+
# constructor contract — internal state the compiled seeding
|
|
416
|
+
# cannot know about — so they construct through the real
|
|
417
|
+
# constructor, then carry the parse's register context.
|
|
418
|
+
if constructs_through_initialize?
|
|
419
|
+
instance = new(lutaml_register: register_id)
|
|
420
|
+
return instance
|
|
421
|
+
end
|
|
410
422
|
instance = allocate
|
|
411
423
|
instance.finalize_deserialization(register_id)
|
|
412
424
|
instance
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -122,6 +122,8 @@ module Lutaml
|
|
|
122
122
|
# them (compiling here silently dropped pi_settings).
|
|
123
123
|
return opt_out!(:processing_instruction) unless
|
|
124
124
|
mapping.processing_instruction_mappings.empty?
|
|
125
|
+
# Wildcard attribute families ride the interpretive path too
|
|
126
|
+
return opt_out!(:all_attributes) if mapping.all_attributes_rule
|
|
125
127
|
|
|
126
128
|
rows = []
|
|
127
129
|
attr_rows = [] # [[rule, attr, row_name]] hydration metadata
|
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Lutaml
|
|
|
38
38
|
@xml_instance
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
attr_reader :root_element, :namespace_uri, :namespace_prefix, :element_sequence, :mappings_imported, :namespace_class, :namespace_param, :element_name, :documentation_text, :type_name_value, :namespace_scope, :mapper_class, :xml_space, :consolidation_maps, :processing_instruction_mappings, :any_element_rule
|
|
41
|
+
attr_reader :root_element, :namespace_uri, :namespace_prefix, :element_sequence, :mappings_imported, :namespace_class, :namespace_param, :element_name, :documentation_text, :type_name_value, :namespace_scope, :mapper_class, :xml_space, :consolidation_maps, :processing_instruction_mappings, :any_element_rule, :all_attributes_rule
|
|
42
42
|
|
|
43
43
|
def initialize
|
|
44
44
|
super
|
|
@@ -49,6 +49,7 @@ module Lutaml
|
|
|
49
49
|
@element_sequence = []
|
|
50
50
|
@content_mapping = nil
|
|
51
51
|
@raw_mapping = nil
|
|
52
|
+
@all_attributes_rule = nil
|
|
52
53
|
@mixed_content = false
|
|
53
54
|
@xml_space = nil
|
|
54
55
|
@format = :xml
|
|
@@ -875,6 +876,26 @@ module Lutaml
|
|
|
875
876
|
)
|
|
876
877
|
end
|
|
877
878
|
|
|
879
|
+
# Capture every attribute not claimed by an explicit map_attribute
|
|
880
|
+
# (and matching an optional pattern) into a Hash attribute, and emit
|
|
881
|
+
# it back as attributes after the mapped ones on serialization —
|
|
882
|
+
# the remainder semantics of map_all, for attribute families
|
|
883
|
+
# (lutaml-model#1031): HTML data-*, ARIA aria-*, on* handlers.
|
|
884
|
+
#
|
|
885
|
+
# @param to [Symbol] the Hash attribute name
|
|
886
|
+
# @param matching [Regexp, nil] optional name filter; nil captures
|
|
887
|
+
# every unclaimed attribute
|
|
888
|
+
#
|
|
889
|
+
# @example
|
|
890
|
+
# xml do
|
|
891
|
+
# element "mn"
|
|
892
|
+
# map_attribute "name", to: :name
|
|
893
|
+
# map_all_attributes to: :other_attributes, matching: /\A(data-|aria-|on)/
|
|
894
|
+
# end
|
|
895
|
+
def map_all_attributes(to:, matching: nil)
|
|
896
|
+
@all_attributes_rule = AllAttributesMapping.new(to, matching)
|
|
897
|
+
end
|
|
898
|
+
|
|
878
899
|
def sequence(&block)
|
|
879
900
|
@element_sequence << ::Lutaml::Model::Sequence.new(self,
|
|
880
901
|
format: :xml).tap do |s|
|
|
@@ -1681,5 +1702,21 @@ module Lutaml
|
|
|
1681
1702
|
self.class.new(@target.dup, @to)
|
|
1682
1703
|
end
|
|
1683
1704
|
end
|
|
1705
|
+
|
|
1706
|
+
# Wildcard attribute-family mapping (#1031): the target Hash collects
|
|
1707
|
+
# every attribute no explicit map_attribute claims and that matches
|
|
1708
|
+
# the optional pattern; serialization emits each pair as an attribute.
|
|
1709
|
+
class AllAttributesMapping
|
|
1710
|
+
attr_reader :to, :matching
|
|
1711
|
+
|
|
1712
|
+
def initialize(to, matching)
|
|
1713
|
+
@to = to
|
|
1714
|
+
@matching = matching
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1717
|
+
def dup
|
|
1718
|
+
self.class.new(@to, @matching)
|
|
1719
|
+
end
|
|
1720
|
+
end
|
|
1684
1721
|
end
|
|
1685
1722
|
end
|
|
@@ -503,11 +503,50 @@ module Lutaml
|
|
|
503
503
|
apply_processing_instruction_deserialization(doc, instance, xml_mapping,
|
|
504
504
|
effective_register)
|
|
505
505
|
|
|
506
|
+
apply_all_attributes_deserialization(doc, instance, xml_mapping)
|
|
507
|
+
|
|
506
508
|
run_consolidation(instance, effective_register, instance_is_serialize)
|
|
507
509
|
|
|
508
510
|
instance
|
|
509
511
|
end
|
|
510
512
|
|
|
513
|
+
# #1031: wildcard attribute families. After the explicit attribute
|
|
514
|
+
# rules have consumed their names, every remaining unprefixed
|
|
515
|
+
# attribute matching the rule's pattern lands in the target Hash —
|
|
516
|
+
# names claimed by explicit rules never leak into it.
|
|
517
|
+
def apply_all_attributes_deserialization(doc, instance, xml_mapping)
|
|
518
|
+
rule = xml_mapping.all_attributes_rule
|
|
519
|
+
return if rule.nil?
|
|
520
|
+
|
|
521
|
+
element = doc.is_a?(::Lutaml::Xml::XmlElement) ? doc : doc.root
|
|
522
|
+
return if element.nil?
|
|
523
|
+
|
|
524
|
+
claimed = attribute_names_claimed_by_rules(xml_mapping)
|
|
525
|
+
remainder = {}
|
|
526
|
+
element.attributes.each_value do |attr|
|
|
527
|
+
name = attr.unprefixed_name.to_s
|
|
528
|
+
next if claimed.include?(name)
|
|
529
|
+
next if rule.matching && !rule.matching.match?(name)
|
|
530
|
+
|
|
531
|
+
remainder[name] = attr.value
|
|
532
|
+
end
|
|
533
|
+
return if remainder.empty?
|
|
534
|
+
|
|
535
|
+
instance.public_send(:"#{rule.to}=", remainder)
|
|
536
|
+
instance.value_set_for(rule.to) if instance.is_a?(::Lutaml::Model::Serialize)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Attribute names claimed by explicit map_attribute rules, plus the
|
|
540
|
+
# engine-handled schemaLocation (which renders through
|
|
541
|
+
# raw_schema_location, not the remainder).
|
|
542
|
+
def attribute_names_claimed_by_rules(xml_mapping)
|
|
543
|
+
names = %w[schemaLocation]
|
|
544
|
+
xml_mapping.mapping_attributes_hash.each_value do |rule_or_array|
|
|
545
|
+
Array(rule_or_array).each { |rule| names << rule.name.to_s }
|
|
546
|
+
end
|
|
547
|
+
names
|
|
548
|
+
end
|
|
549
|
+
|
|
511
550
|
def validate_document!(doc, options)
|
|
512
551
|
return unless doc.is_a?(Array)
|
|
513
552
|
|
|
@@ -101,6 +101,9 @@ module Lutaml
|
|
|
101
101
|
apply_standard_rules(root, model_instance, options)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# The wildcard attribute family emits after the mapped attributes
|
|
105
|
+
apply_all_attributes_mappings(root, model_instance, mapping)
|
|
106
|
+
|
|
104
107
|
root
|
|
105
108
|
end
|
|
106
109
|
|
|
@@ -242,6 +245,27 @@ module Lutaml
|
|
|
242
245
|
end
|
|
243
246
|
|
|
244
247
|
# Check if element order should be used for serialization
|
|
248
|
+
|
|
249
|
+
# #1031: emit the wildcard attribute family after the mapped
|
|
250
|
+
# attributes. Attributes render in insertion order; the remainder
|
|
251
|
+
# follows the explicitly mapped names.
|
|
252
|
+
def apply_all_attributes_mappings(root, model_instance, mapping)
|
|
253
|
+
rule = mapping.all_attributes_rule
|
|
254
|
+
return if rule.nil?
|
|
255
|
+
|
|
256
|
+
value = model_instance.public_send(rule.to)
|
|
257
|
+
return if value.nil? || !value.is_a?(::Hash)
|
|
258
|
+
|
|
259
|
+
value.each do |name, attr_value|
|
|
260
|
+
next if attr_value.nil?
|
|
261
|
+
|
|
262
|
+
root.add_attribute(
|
|
263
|
+
::Lutaml::Xml::DataModel::XmlAttribute.new(name.to_s,
|
|
264
|
+
attr_value.to_s, nil),
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
245
269
|
#
|
|
246
270
|
# @param model_instance [Object] The model instance
|
|
247
271
|
# @param mapping [Xml::Mapping] The mapping
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# #1031: wildcard attribute families — every attribute no explicit
|
|
6
|
+
# map_attribute claims and that matches the pattern lands in a Hash and
|
|
7
|
+
# is emitted back after the mapped attributes on serialization.
|
|
8
|
+
RSpec.describe "map_all_attributes" do
|
|
9
|
+
let(:model_class) do
|
|
10
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
11
|
+
attribute :name, :string
|
|
12
|
+
attribute :data_attributes, :hash
|
|
13
|
+
|
|
14
|
+
xml do
|
|
15
|
+
element "mn"
|
|
16
|
+
map_attribute "name", to: :name
|
|
17
|
+
map_all_attributes to: :data_attributes, matching: /\A(data-|aria-|on)/
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
let(:stubbed) do
|
|
23
|
+
stub_const("MapAllAttributesModel", model_class)
|
|
24
|
+
MapAllAttributesModel
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "captures unclaimed, pattern-matching attributes into the Hash" do
|
|
28
|
+
xml = '<mn name="x" data-title="T" onclick="f()" aria-label="L" unre="nope"/>'
|
|
29
|
+
model = stubbed.from_xml(xml)
|
|
30
|
+
|
|
31
|
+
expect(model.name).to eq("x")
|
|
32
|
+
expect(model.data_attributes).to eq(
|
|
33
|
+
"data-title" => "T", "onclick" => "f()", "aria-label" => "L",
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "never lets mapped names leak into the remainder" do
|
|
38
|
+
xml = '<mn name="x" data-name="D"/>'
|
|
39
|
+
model = stubbed.from_xml(xml)
|
|
40
|
+
|
|
41
|
+
expect(model.data_attributes).to eq("data-name" => "D")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "emits the remainder after the mapped attributes on serialization" do
|
|
45
|
+
model = stubbed.from_xml(
|
|
46
|
+
'<mn name="x" data-title="T" onclick="f()"/>',
|
|
47
|
+
)
|
|
48
|
+
out = model.to_xml
|
|
49
|
+
|
|
50
|
+
expect(out).to include('name="x"')
|
|
51
|
+
expect(out).to include('data-title="T"')
|
|
52
|
+
expect(out).to include('onclick="f()"')
|
|
53
|
+
expect(out.index("name=")).to be < out.index("data-title=")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "round-trips losslessly" do
|
|
57
|
+
xml = '<mn name="x" data-title="T" onclick="f()" aria-label="L"/>'
|
|
58
|
+
model = stubbed.from_xml(xml)
|
|
59
|
+
round_tripped = stubbed.from_xml(model.to_xml)
|
|
60
|
+
|
|
61
|
+
expect(round_tripped.data_attributes).to eq(model.data_attributes)
|
|
62
|
+
expect(round_tripped.name).to eq(model.name)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "emits nothing when the remainder is empty" do
|
|
66
|
+
model = stubbed.from_xml('<mn name="x"/>')
|
|
67
|
+
out = model.to_xml
|
|
68
|
+
|
|
69
|
+
expect(out).not_to include("data-")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "captures every unclaimed attribute when no pattern is given" do
|
|
73
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
74
|
+
attribute :name, :string
|
|
75
|
+
attribute :other_attributes, :hash
|
|
76
|
+
|
|
77
|
+
xml do
|
|
78
|
+
element "el"
|
|
79
|
+
map_attribute "name", to: :name
|
|
80
|
+
map_all_attributes to: :other_attributes
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
stub_const("MapAllNoPatternModel", klass)
|
|
84
|
+
|
|
85
|
+
model = klass.from_xml('<el name="x" zeta="1"/>')
|
|
86
|
+
|
|
87
|
+
expect(model.other_attributes).to eq("zeta" => "1")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# A class that defines its own initialize is built through it by the
|
|
6
|
+
# from_* walk, and the walk hands it the register exactly as
|
|
7
|
+
# `new(lutaml_register:)` did before the compiled seed existed: a
|
|
8
|
+
# constructor that tells a blank construction from a caller's by that
|
|
9
|
+
# keyword sees the same call either way.
|
|
10
|
+
RSpec.describe "Walk construction of a custom-initialize class" do
|
|
11
|
+
let(:model) do
|
|
12
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
13
|
+
attribute :name, :string
|
|
14
|
+
|
|
15
|
+
json do
|
|
16
|
+
map "name", to: :name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
attr_accessor :constructions
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(*args, **kwargs)
|
|
24
|
+
self.class.constructions << [args, kwargs.keys]
|
|
25
|
+
@internal = :ready
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :internal
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
before do
|
|
34
|
+
stub_const("WalkConstructorModel", model)
|
|
35
|
+
model.constructions = []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "runs the class's own initialize" do
|
|
39
|
+
expect(model.from_json('{"name":"a"}').internal).to eq(:ready)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "passes the register, as the walk did through new(lutaml_register:)" do
|
|
43
|
+
parsed = model.from_json('{"name":"a"}')
|
|
44
|
+
|
|
45
|
+
expect(model.constructions).to eq([[[], [:lutaml_register]]])
|
|
46
|
+
expect(parsed.name).to eq("a")
|
|
47
|
+
end
|
|
48
|
+
end
|
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.138
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -1796,6 +1796,7 @@ files:
|
|
|
1796
1796
|
- spec/lutaml/model/liquid/indexed_access_spec.rb
|
|
1797
1797
|
- spec/lutaml/model/liquid_compatibility_spec.rb
|
|
1798
1798
|
- spec/lutaml/model/logger_spec.rb
|
|
1799
|
+
- spec/lutaml/model/map_all_attributes_spec.rb
|
|
1799
1800
|
- spec/lutaml/model/map_all_spec.rb
|
|
1800
1801
|
- spec/lutaml/model/map_content_spec.rb
|
|
1801
1802
|
- spec/lutaml/model/map_unmapped_spec.rb
|
|
@@ -1912,6 +1913,7 @@ files:
|
|
|
1912
1913
|
- spec/lutaml/model/value_map_spec.rb
|
|
1913
1914
|
- spec/lutaml/model/value_policy_spec.rb
|
|
1914
1915
|
- spec/lutaml/model/value_transformation_comprehensive_spec.rb
|
|
1916
|
+
- spec/lutaml/model/walk_constructor_spec.rb
|
|
1915
1917
|
- spec/lutaml/model/with_child_mapping_spec.rb
|
|
1916
1918
|
- spec/lutaml/model/writer_assigned_attribute_spec.rb
|
|
1917
1919
|
- spec/lutaml/model/xml_decoupling_spec.rb
|