lutaml-model 0.8.84 → 0.8.85

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.
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ module Leptris
6
+ # Serialize-side mirror of the plan fast path: builds the output
7
+ # tree directly through leptris's fused create_child chain — no
8
+ # XmlElement tree, no DeclarationPlanner pass, no builder context
9
+ # stack, no moxml wrapper minting. Serialized shapes must be the
10
+ # plain ones (attributes, scalars, collections, nested models,
11
+ # raw fragments, content runs); custom-method, polymorphic,
12
+ # union, and multi-spelling rows keep the interpretive serializer
13
+ # (their output flows through hash-shaped custom APIs).
14
+ #
15
+ # Ordered/mixed models serialize from element_order when the
16
+ # instance carries one (parsed models): text runs, comments, and
17
+ # element entries in document order, mirroring the interpretive
18
+ # order applier — including its content-attr-first text reads
19
+ # (mutations to the model show up) and CDATA flattening. Models
20
+ # without element_order fall back interpretively (return nil).
21
+ module PlanSerializer
22
+ SERIALIZABLE_KINDS = %i[scalar collection_native collection_cb
23
+ nested ordered_deferred raw content
24
+ content_deferred].freeze
25
+ CONTENT_KINDS = %i[content content_deferred].freeze
26
+
27
+ class << self
28
+ # plan: the compiler's entry for instance's class
29
+ def call(instance, plan, register = nil)
30
+ # The child model's declared lutaml_default_register takes
31
+ # precedence over the ambient register — the resolve_for_child
32
+ # contract (#876); without it, child plans and attribute
33
+ # serialization resolve in the parent context.
34
+ register = Lutaml::Model::Register.resolve_for_child(
35
+ instance.class,
36
+ register || Lutaml::Model::Config.default_register,
37
+ )
38
+ return nil if plan[:ordered] && instance.element_order.nil?
39
+
40
+ doc = ::Leptris::XML::Document.create
41
+ root = doc.create_element(plan[:tree][:name])
42
+ doc.root = root
43
+ if plan[:ordered]
44
+ build_ordered(root, instance, plan, doc, register)
45
+ else
46
+ build(root, instance, plan, doc, register)
47
+ end
48
+ doc.to_xml(indent: 2, no_decl: true)
49
+ end
50
+
51
+ # Whether the compiled plan is serialize-shaped. Ordered/mixed
52
+ # models serialize through build_ordered from element_order;
53
+ # plain models through plan rows.
54
+ def serializable?(plan)
55
+ plan[:rows].all? do |_rule, _attr, kind, _sp, _del|
56
+ SERIALIZABLE_KINDS.include?(kind)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def register
63
+ Lutaml::Model::Config.default_register
64
+ end
65
+
66
+ def build(element, instance, plan, doc, reg = nil)
67
+ reg ||= register
68
+ write_attributes(element, instance, plan, reg)
69
+ plan[:rows].each do |rule, attr, kind, spelling, delegate|
70
+ value = value_of(instance, attr, delegate)
71
+ next unless rule.render?(value, instance)
72
+
73
+ case kind
74
+ when :scalar
75
+ # Multi-capture parity: an attribute that collected
76
+ # several occurrences serializes one element per item,
77
+ # as the interpretive writer does. A plain scalar skips
78
+ # the Array() wrap — the common case.
79
+ if value.is_a?(::Array)
80
+ value.each do |item|
81
+ add_leaf(element, row_name(rule, spelling),
82
+ attr.serialize(item, :xml, reg), doc,
83
+ attrs: rule.when_attribute)
84
+ end
85
+ else
86
+ add_leaf(element, row_name(rule, spelling),
87
+ attr.serialize(value, :xml, reg), doc,
88
+ attrs: rule.when_attribute)
89
+ end
90
+ when :collection_native, :collection_cb
91
+ Array(value).each do |item|
92
+ add_leaf(element, rule.name.to_s,
93
+ attr.serialize(item, :xml, reg), doc,
94
+ attrs: rule.when_attribute)
95
+ end
96
+ when :nested, :ordered_deferred
97
+ Array(value).each do |item|
98
+ child_plan = PlanCompiler.compile(item.class, reg)
99
+ child = element.create_child(child_plan[:tree][:name])
100
+ if child_plan[:ordered]
101
+ return nil unless build_ordered(child, item, child_plan,
102
+ doc, reg)
103
+
104
+ else
105
+ build(child, item, child_plan, doc, reg)
106
+ end
107
+ end
108
+ when :raw
109
+ Array(value).each { |raw| element.add_child(raw.to_s) }
110
+ when :content, :content_deferred
111
+ Array(value).each { |run| element.add_child(doc.create_text_node(run.to_s)) }
112
+ end
113
+ end
114
+ end
115
+
116
+ # Element_order-driven emission (the interpretive order
117
+ # applier's contract): text runs and comments verbatim, each
118
+ # element entry consuming the next item of its collection
119
+ # (document order). Text reads prefer the content attribute
120
+ # when it lines up with the text-node count, so mutations to
121
+ # the model are reflected; element_order text is the fallback.
122
+ # PIs drop (interpretive parity). Returns nil when a nested
123
+ # ordered child lacks element_order (caller falls back).
124
+ def build_ordered(element, instance, plan, doc, reg = nil)
125
+ reg ||= register
126
+ write_attributes(element, instance, plan, reg)
127
+ rows_by_name = {}
128
+ plan[:rows].each do |rule, attr, kind, spelling, delegate|
129
+ next unless rule.name
130
+
131
+ rows_by_name[rule.name.to_s] ||= [rule, attr, kind, spelling,
132
+ delegate]
133
+ end
134
+ content_entry = plan[:rows].find do |rule, _attr, kind, _sp, _del|
135
+ CONTENT_KINDS.include?(kind) && rule.name.nil?
136
+ end
137
+ order = instance.element_order
138
+ element_indices = ::Hash.new(0)
139
+ content_attr = content_entry && content_entry[1]
140
+ content_value = content_attr &&
141
+ value_of(instance, content_attr, content_entry[4])
142
+ text_node_count = order.count { |o| o.type == "Text" }
143
+ use_content_index = content_value.is_a?(Array) &&
144
+ content_value.length == text_node_count
145
+ content_cdata = content_entry && content_entry[0].cdata
146
+ text_node_index = 0
147
+
148
+ order.each do |object|
149
+ case object.type
150
+ when "Text"
151
+ text = if content_attr && !content_value.nil?
152
+ if use_content_index
153
+ content_value[text_node_index].to_s
154
+ elsif !content_value.is_a?(Array) && text_node_count <= 1
155
+ content_value.to_s
156
+ else
157
+ object.text_content || object.name
158
+ end
159
+ else
160
+ object.text_content || object.name
161
+ end
162
+ text_node_index += 1
163
+ next if text.nil?
164
+ # Mixed content keeps whitespace; ordered-only skips it
165
+ next if content_attr.nil? && text.strip.empty?
166
+
167
+ element.add_child(content_cdata ? doc.create_cdata(text) : doc.create_text_node(text))
168
+ when "Comment"
169
+ element.add_child(doc.create_comment(object.text_content))
170
+ when "Element"
171
+ entry = rows_by_name[object.name]
172
+ next unless entry
173
+
174
+ _, attr, kind, _spelling, delegate = entry
175
+ value = value_of(instance, attr, delegate)
176
+ case kind
177
+ when :collection_native, :collection_cb
178
+ index = element_indices[object.name]
179
+ items = Array(value)
180
+ next unless index < items.length
181
+
182
+ element_indices[object.name] += 1
183
+ add_leaf(element, object.name,
184
+ attr.serialize(items[index], :xml, reg), doc)
185
+ when :nested, :ordered_deferred
186
+ item = if attr.collection?
187
+ index = element_indices[object.name]
188
+ element_indices[object.name] += 1
189
+ Array(value)[index]
190
+ else
191
+ value
192
+ end
193
+ next unless item
194
+
195
+ child_plan = PlanCompiler.compile(item.class, reg)
196
+ child = element.create_child(child_plan[:tree][:name])
197
+ if child_plan[:ordered]
198
+ return nil unless build_ordered(child, item, child_plan,
199
+ doc, reg)
200
+ else
201
+ build(child, item, child_plan, doc, reg)
202
+ end
203
+ when :raw
204
+ element.add_child(value.to_s) unless value.nil?
205
+ when :scalar
206
+ add_leaf(element, object.name,
207
+ attr.serialize(value, :xml, reg), doc)
208
+ end
209
+ end
210
+ end
211
+ true
212
+ end
213
+
214
+ # Attributes in document order when the instance recorded it
215
+ # (parsed models), else plan row order.
216
+ def write_attributes(element, instance, plan, _reg = nil)
217
+ recorded = instance.attribute_order
218
+ if recorded && !recorded.empty?
219
+ by_name = {}
220
+ plan[:attr_rows].each do |rule, attr, _sp, delegate|
221
+ by_name[rule.name.to_s] = [rule, attr, delegate]
222
+ end
223
+ names = recorded |
224
+ plan[:attr_rows].map { |rule, _a, _s, _d| rule.name.to_s }
225
+ names.each do |name|
226
+ write_attribute(element, instance, name, by_name[name])
227
+ end
228
+ else
229
+ plan[:attr_rows].each do |rule, attr, _sp, delegate|
230
+ write_attribute(element, instance, rule.name.to_s,
231
+ [rule, attr, delegate])
232
+ end
233
+ end
234
+ end
235
+
236
+ def write_attribute(element, instance, name, entry, reg = nil)
237
+ return unless entry
238
+
239
+ _rule, attr, delegate = entry
240
+ value = value_of(instance, attr, delegate)
241
+ return if value.nil?
242
+
243
+ element.set_attribute(name,
244
+ attr.serialize(value, :xml, reg || register).to_s)
245
+ end
246
+
247
+ # Partition rows (#88) re-emit their discriminator: the wire
248
+ # element carries the when_attribute pairs so the round trip
249
+ # re-routes to the same attribute on reparse.
250
+ def add_leaf(element, name, value, doc, attrs: nil)
251
+ child = element.create_child(name)
252
+ attrs&.each { |k, v| child[k.to_s] = v.to_s }
253
+ child.add_child(doc.create_text_node(value.to_s)) unless value.nil?
254
+ child
255
+ end
256
+
257
+ def value_of(instance, attr, delegate)
258
+ if delegate
259
+ target = instance.public_send(delegate)
260
+ target&.public_send(attr.name)
261
+ else
262
+ instance.public_send(attr.name)
263
+ end
264
+ end
265
+
266
+ def row_name(rule, spelling)
267
+ (spelling || rule.name).to_s
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ module Leptris
6
+ # Public consumer surface for the plan fast path: one engine
7
+ # parse + descriptor walk and direct model hydration, bypassing
8
+ # the wrapper-tree construction that dominates large-document
9
+ # consumer paths. The returned object is the mapped model root;
10
+ # collection attributes remain collections on that model.
11
+ module PlanWalk
12
+ class << self
13
+ # Build the plan, walk the document, and hydrate the mapped
14
+ # root. Returns nil when the model is not plan-compilable.
15
+ def call(model_class, xml, _options = {})
16
+ register = Lutaml::Model::Config.default_register
17
+ plan = PlanCompiler.compile(model_class, register)
18
+ return nil unless plan
19
+
20
+ document = ::Leptris::XML.parse(xml)
21
+ root = document.root
22
+ return nil unless root && root.name == plan[:tree][:name]
23
+
24
+ PlanHydrator.call(model_class, plan,
25
+ plan[:descriptor].walk(root),
26
+ node: root)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ # The leptris plan fast path: model mapping to libleptris descriptor
6
+ # plan, native walk, direct hydration. Everything under this
7
+ # namespace is bound to the leptris gem; nothing above it may
8
+ # reference leptris types directly (the moxml adapters and the
9
+ # interpretive path never enter here).
10
+ module Leptris
11
+ # The plan path requires the one-crossing child snapshot
12
+ # (leptris_plan_value_children_snapshot with child handles,
13
+ # PlanValue#children_snapshot), which shipped in leptris
14
+ # 1.9.273.0. Bundles resolving an older gem keep the
15
+ # interpretive path — PlanCompiler.compile answers nil — the
16
+ # existing opt-out protocol.
17
+ MIN_LEPTRIS_VERSION = "1.9.273.0"
18
+
19
+ def self.plan_path_compatible?
20
+ return false unless defined?(Gem)
21
+
22
+ spec = Gem.loaded_specs["leptris"]
23
+ spec && spec.version >= Gem::Version.new(MIN_LEPTRIS_VERSION)
24
+ end
25
+
26
+ autoload :PlanCompiler, "lutaml/xml/leptris/plan_compiler"
27
+ autoload :PlanHydrator, "lutaml/xml/leptris/plan_hydrator"
28
+ autoload :PlanOrder, "lutaml/xml/leptris/plan_order"
29
+ autoload :PlanSerializer, "lutaml/xml/leptris/plan_serializer"
30
+ autoload :PlanWalk, "lutaml/xml/leptris/plan_walk"
31
+ end
32
+ end
33
+ end
@@ -63,7 +63,11 @@ module Lutaml
63
63
  #
64
64
  # @return [Array<String>]
65
65
  def xml_schema_paths
66
- inherited = if superclass.respond_to?(:xml_schema_paths)
66
+ # Type check, not a probe: every Serializable carries the
67
+ # class method; a plain (non-Serializable) superclass ends
68
+ # the chain.
69
+ inherited = if superclass.is_a?(Class) &&
70
+ superclass.include?(Lutaml::Model::Serialize)
67
71
  superclass.xml_schema_paths
68
72
  else
69
73
  []
@@ -428,7 +428,7 @@ text_node_count = 0, use_content_index = false)
428
428
  # reconciliation could not place
429
429
  def apply_remaining_rules(model_instance, options, compiled_rules,
430
430
  mapping, processed_text_nodes, fallback_rules)
431
- attr_order = model_instance.respond_to?(:attribute_order) &&
431
+ attr_order = model_instance.is_a?(Lutaml::Model::Serialize) &&
432
432
  model_instance.attribute_order
433
433
 
434
434
  rules_to_apply = if attr_order
@@ -47,7 +47,7 @@ end
47
47
 
48
48
  Lutaml::Model::Config.instance.xml_plan_fast_path = true
49
49
  measure.call("PlanWalk parse + 5000 model.new") do
50
- result = Lutaml::Xml::PlanWalk.call(PlanWalkProbe::BulkRoot, xml)
50
+ result = Lutaml::Xml::Leptris::PlanWalk.call(PlanWalkProbe::BulkRoot, xml)
51
51
  raise "PlanWalk returned nil" unless result&.item&.size == 5000
52
52
  end
53
53
 
@@ -86,14 +86,18 @@ RSpec.describe Lutaml::Model::Store do
86
86
  end
87
87
 
88
88
  it "does not mix indices across different classes" do
89
- model_class.new(id: "a")
90
- other_class.new(id: "a")
89
+ # Hold strong references: Store keeps WeakRefs, so unreferenced
90
+ # instances can be collected between registration and resolve,
91
+ # making this example fail only under in-suite GC pressure.
92
+ a = model_class.new(id: "a")
93
+ b = other_class.new(id: "a")
91
94
 
92
95
  result = described_class.resolve(model_class, :id, "a")
93
96
  expect(result).to be_a(model_class)
94
97
 
95
98
  result2 = described_class.resolve(other_class, :id, "a")
96
99
  expect(result2).to be_a(other_class)
100
+ expect([a, b]).to all(be_a(Lutaml::Model::Serializable))
97
101
  end
98
102
 
99
103
  it "registering class B does not iterate class A's indices" do
@@ -57,7 +57,7 @@ RSpec.describe "XML plan fast path" do
57
57
  end
58
58
  stub_const("PlanFastPath::SelfRef", node)
59
59
 
60
- expect(Lutaml::Xml::PlanCompiler.compile(node, :default)).to be_nil
60
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(node, :default)).to be_nil
61
61
 
62
62
  xml = "<n>#{'<n>t</n>' * 200}</n>"
63
63
  parsed = node.from_xml(xml)
@@ -94,7 +94,7 @@ RSpec.describe "XML plan fast path" do
94
94
 
95
95
  expect(parsed.xmi_type).to eq("uml:Parameter")
96
96
  expect(parsed.classifier_type).to eq("EAnone_void")
97
- expect(Lutaml::Xml::PlanCompiler.compile(parameter, :default)).to be_nil
97
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(parameter, :default)).to be_nil
98
98
  end
99
99
 
100
100
  it "is on by default and can be disabled" do
@@ -156,7 +156,7 @@ RSpec.describe "XML plan fast path" do
156
156
  end
157
157
  end
158
158
 
159
- expect(Lutaml::Xml::PlanCompiler.compile(klass,
159
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(klass,
160
160
  Lutaml::Model::Config.default_register)).not_to be_nil
161
161
  expect(klass.from_xml("<doc><note>hi</note></doc>").note).to eq("HI")
162
162
  end
@@ -223,14 +223,14 @@ RSpec.describe "XML plan fast path" do
223
223
  end
224
224
  end
225
225
 
226
- expect(Lutaml::Xml::PlanCompiler.compile(klass,
226
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(klass,
227
227
  Lutaml::Model::Config.default_register)).not_to be_nil
228
228
  parsed = klass.from_xml("<doc><note>hi</note></doc>")
229
229
  expect(parsed.tgt.note).to eq("hi")
230
230
  end
231
231
 
232
232
  it "routes a single collection row natively (hybrid routing)" do
233
- plan = Lutaml::Xml::PlanCompiler.compile(
233
+ plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
234
234
  Class.new(Lutaml::Model::Serializable) do
235
235
  attribute :xs, :string, collection: true
236
236
  attribute :name, :string
@@ -400,7 +400,7 @@ RSpec.describe "XML plan fast path" do
400
400
  map_element "para", to: :para
401
401
  end
402
402
  end
403
- plan = Lutaml::Xml::PlanCompiler.compile(
403
+ plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
404
404
  doc_class, Lutaml::Model::Config.default_register
405
405
  )
406
406
  expect(plan).not_to be_nil
@@ -623,7 +623,7 @@ RSpec.describe "XML plan fast path" do
623
623
  expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
624
624
  expect(req.purpose.map(&:text)).to eq(["p1"])
625
625
 
626
- expect(Lutaml::Xml::PlanCompiler.compile(partitioned,
626
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(partitioned,
627
627
  :default)).to be_nil
628
628
  end
629
629
 
@@ -708,7 +708,7 @@ RSpec.describe "XML plan fast path" do
708
708
  stub_const("PlanFastPath::UpdateFields847", klass)
709
709
 
710
710
  expect(klass.new(value: false).to_xml).to include("http://example.com/ord847")
711
- plan = Lutaml::Xml::PlanCompiler.compile(
711
+ plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
712
712
  klass, Lutaml::Model::Config.default_register
713
713
  )
714
714
  expect(plan[:namespaced]).to be(true)
@@ -37,7 +37,7 @@ RSpec.describe "Plan fast path with when_attribute partitions" do
37
37
  end
38
38
 
39
39
  it "compiles the partitioned model with row tags" do
40
- plan = Lutaml::Xml::PlanCompiler.compile(
40
+ plan = Lutaml::Xml::Leptris::PlanCompiler.compile(
41
41
  model, Lutaml::Model::Config.default_register
42
42
  )
43
43
  expect(plan).not_to be_nil
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
- require "lutaml/xml/plan_walk"
4
+ require "lutaml/xml/leptris/plan_walk"
5
5
 
6
6
  RSpec.describe "XML PlanWalk consumer surface" do
7
7
  around do |example|
@@ -25,7 +25,7 @@ RSpec.describe "XML PlanWalk consumer surface" do
25
25
 
26
26
  it "hydrates equal to the interpretive path on a single-row document" do
27
27
  xml = %(<row id="7"><name>Alice</name><tags>x</tags><tags>y</tags></row>)
28
- walked = Lutaml::Xml::PlanWalk.call(item_class, xml)
28
+ walked = Lutaml::Xml::Leptris::PlanWalk.call(item_class, xml)
29
29
  expected = item_class.from_xml(xml)
30
30
 
31
31
  expect(walked).to be_a(item_class)
@@ -46,7 +46,7 @@ RSpec.describe "XML PlanWalk consumer surface" do
46
46
  end
47
47
  end
48
48
 
49
- walked = Lutaml::Xml::PlanWalk.call(root_class, xml)
49
+ walked = Lutaml::Xml::Leptris::PlanWalk.call(root_class, xml)
50
50
  expected = root_class.from_xml(xml)
51
51
 
52
52
  expect(walked.item).to be_an(Array)
@@ -71,7 +71,7 @@ RSpec.describe "XML PlanWalk consumer surface" do
71
71
  end
72
72
  end
73
73
 
74
- walked = Lutaml::Xml::PlanWalk.call(root_class, xml)
74
+ walked = Lutaml::Xml::Leptris::PlanWalk.call(root_class, xml)
75
75
  interpretive = root_class.from_xml(xml)
76
76
  expect(walked.item.size).to eq(interpretive.item.size)
77
77
  expect(walked.item.map(&:id)).to eq(interpretive.item.map(&:id))
@@ -94,9 +94,9 @@ RSpec.describe "XML PlanWalk consumer surface" do
94
94
  end
95
95
  end
96
96
 
97
- expect(Lutaml::Xml::PlanCompiler.compile(klass,
97
+ expect(Lutaml::Xml::Leptris::PlanCompiler.compile(klass,
98
98
  Lutaml::Model::Config.default_register)).not_to be_nil
99
- expect(Lutaml::Xml::PlanWalk.call(klass, "<doc><note>x</note></doc>").tgt.note)
99
+ expect(Lutaml::Xml::Leptris::PlanWalk.call(klass, "<doc><note>x</note></doc>").tgt.note)
100
100
  .to eq("x")
101
101
  end
102
102
  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.84
4
+ version: 0.8.85
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -781,7 +781,13 @@ files:
781
781
  - lib/lutaml/xml/format_chooser.rb
782
782
  - lib/lutaml/xml/hoisting_algorithm.rb
783
783
  - lib/lutaml/xml/html_entities.rb
784
+ - lib/lutaml/xml/leptris.rb
784
785
  - lib/lutaml/xml/leptris/element.rb
786
+ - lib/lutaml/xml/leptris/plan_compiler.rb
787
+ - lib/lutaml/xml/leptris/plan_hydrator.rb
788
+ - lib/lutaml/xml/leptris/plan_order.rb
789
+ - lib/lutaml/xml/leptris/plan_serializer.rb
790
+ - lib/lutaml/xml/leptris/plan_walk.rb
785
791
  - lib/lutaml/xml/listener.rb
786
792
  - lib/lutaml/xml/mapping.rb
787
793
  - lib/lutaml/xml/mapping_rule.rb
@@ -810,11 +816,6 @@ files:
810
816
  - lib/lutaml/xml/parse_session.rb
811
817
  - lib/lutaml/xml/parsed_namespace_declaration.rb
812
818
  - lib/lutaml/xml/parsed_namespace_set.rb
813
- - lib/lutaml/xml/plan_compiler.rb
814
- - lib/lutaml/xml/plan_hydrator.rb
815
- - lib/lutaml/xml/plan_order.rb
816
- - lib/lutaml/xml/plan_serializer.rb
817
- - lib/lutaml/xml/plan_walk.rb
818
819
  - lib/lutaml/xml/polymorphic_value_handler.rb
819
820
  - lib/lutaml/xml/qualified_inheritance_strategy.rb
820
821
  - lib/lutaml/xml/rexml.rb