lutaml-model 0.8.56 → 0.8.106
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/.github/scripts/test_installed_gem.rb +58 -0
- data/.github/workflows/downstream-performance.yml +28 -3
- data/.github/workflows/js-pr-check.yml +4 -0
- data/.github/workflows/js-sync.yml +4 -0
- data/.github/workflows/opal.yml +9 -0
- data/.github/workflows/rake.yml +6 -41
- data/.github/workflows/release.yml +41 -0
- data/Gemfile +17 -3
- data/bench/bench_alloc_audit.rb +56 -0
- data/bench/bench_common.rb +11 -0
- data/bench/bench_merge_attempts.rb +49 -0
- data/docs/_guides/gem-integration.adoc +85 -0
- data/docs/_guides/grammar-backed-formats.adoc +69 -0
- data/docs/_guides/index.adoc +8 -1
- data/docs/_guides/native-engines.adoc +23 -5
- data/docs/_guides/schema-generation.adoc +25 -0
- data/docs/_guides/type-registers-contexts.adoc +126 -0
- data/lib/compat/opal/lutaml_model_boot.rb +7 -5
- data/lib/lutaml/json/adapter/yeptris_adapter.rb +24 -1
- data/lib/lutaml/key_value/data_model/element.rb +5 -1
- data/lib/lutaml/key_value/mapping_rule.rb +7 -0
- data/lib/lutaml/key_value/transform.rb +29 -24
- data/lib/lutaml/key_value/transformation/collection_serializer.rb +12 -29
- data/lib/lutaml/key_value/transformation/value_serializer.rb +29 -6
- data/lib/lutaml/key_value/transformation.rb +64 -116
- data/lib/lutaml/model/adapter_resolver.rb +7 -3
- data/lib/lutaml/model/attribute.rb +74 -15
- data/lib/lutaml/model/cached_type_resolver.rb +13 -6
- data/lib/lutaml/model/configuration.rb +13 -0
- data/lib/lutaml/model/mapping/mapping.rb +13 -0
- data/lib/lutaml/model/mapping/mapping_rule.rb +6 -1
- data/lib/lutaml/model/render_policy.rb +26 -8
- data/lib/lutaml/model/schema/base_schema.rb +15 -0
- data/lib/lutaml/model/schema/generator/definition.rb +52 -8
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +35 -14
- data/lib/lutaml/model/schema.rb +4 -2
- data/lib/lutaml/model/serializable.rb +33 -0
- data/lib/lutaml/model/serialize/attribute_definition.rb +196 -2
- data/lib/lutaml/model/serialize/builder.rb +22 -0
- data/lib/lutaml/model/serialize/conversion_caching.rb +5 -0
- data/lib/lutaml/model/serialize/format_conversion.rb +49 -41
- data/lib/lutaml/model/serialize/initialization.rb +54 -7
- data/lib/lutaml/model/serialize/model_import.rb +60 -0
- data/lib/lutaml/model/serialize.rb +14 -6
- data/lib/lutaml/model/type/time.rb +6 -0
- data/lib/lutaml/model/type/value.rb +9 -7
- data/lib/lutaml/model/type_resolver.rb +7 -5
- data/lib/lutaml/model/utils.rb +18 -5
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +7 -0
- data/lib/lutaml/xml/adapter/xml_parser.rb +5 -2
- data/lib/lutaml/xml/adapter_loader.rb +5 -2
- data/lib/lutaml/xml/format.rb +18 -16
- data/lib/lutaml/xml/leptris/native_emission.rb +187 -0
- data/lib/lutaml/xml/leptris/plan_compiler.rb +650 -0
- data/lib/lutaml/xml/leptris/plan_hydrator.rb +748 -0
- data/lib/lutaml/xml/leptris/plan_order.rb +55 -0
- data/lib/lutaml/xml/leptris/plan_serializer.rb +702 -0
- data/lib/lutaml/xml/leptris/plan_walk.rb +36 -0
- data/lib/lutaml/xml/leptris.rb +46 -0
- data/lib/lutaml/xml/mapping.rb +71 -18
- data/lib/lutaml/xml/model_transform.rb +231 -8
- data/lib/lutaml/xml/namespace.rb +18 -8
- data/lib/lutaml/xml/namespace_collector.rb +17 -1
- data/lib/lutaml/xml/namespace_needs.rb +77 -49
- data/lib/lutaml/xml/parse_session.rb +7 -19
- data/lib/lutaml/xml/schema/xsd/schema.rb +2 -1
- data/lib/lutaml/xml/serialization/format_conversion.rb +5 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +21 -15
- data/lib/lutaml/xml/transformation.rb +15 -1
- data/lib/lutaml/xml/type/serializers.rb +8 -1
- data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +34 -2
- data/lib/tasks/performance_probe_plan_walk.rb +1 -1
- data/lutaml-model.gemspec +7 -4
- data/spec/lutaml/json/yeptris_adapter_spec.rb +7 -2
- data/spec/lutaml/key_value/transformation/value_serializer_spec.rb +9 -5
- data/spec/lutaml/key_value/transformation_spec.rb +87 -21
- data/spec/lutaml/model/any_element_mapping_spec.rb +41 -0
- data/spec/lutaml/model/collection_validation_spec.rb +265 -0
- data/spec/lutaml/model/desc_spec.rb +50 -0
- data/spec/lutaml/model/late_attribute_declaration_spec.rb +35 -0
- data/spec/lutaml/model/model_import_reentry_spec.rb +46 -0
- data/spec/lutaml/model/opal_smoke_spec.rb +17 -0
- data/spec/lutaml/model/schema/generate_many_spec.rb +61 -0
- data/spec/lutaml/model/schema/generation_regressions_spec.rb +139 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +1 -13
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +0 -11
- data/spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb +244 -0
- data/spec/lutaml/model/store_spec.rb +6 -2
- data/spec/lutaml/model/unregistered_context_resolution_spec.rb +34 -0
- data/spec/lutaml/model/writer_assigned_attribute_spec.rb +62 -0
- data/spec/lutaml/model/yeptris_error_parity_spec.rb +95 -0
- data/spec/lutaml/xml/adapter/empty_document_diagnostics_spec.rb +50 -0
- data/spec/lutaml/xml/adapter/over_cap_parse_spec.rb +105 -0
- data/spec/lutaml/xml/adapter_loader_spec.rb +14 -0
- data/spec/lutaml/xml/adapter_metadata_default_spec.rb +16 -0
- data/spec/lutaml/xml/attribute_ambiguity_surface_spec.rb +5 -0
- data/spec/lutaml/xml/clear_parse_state_spec.rb +6 -1
- data/spec/lutaml/xml/compiled_rule_namespace_spec.rb +5 -0
- data/spec/lutaml/xml/cross_register_plan_spec.rb +75 -0
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +16 -8
- data/spec/lutaml/xml/element_order_tracking_spec.rb +37 -0
- data/spec/lutaml/xml/leptris_string_encoding_spec.rb +65 -0
- data/spec/lutaml/xml/opal_xml_spec.rb +47 -0
- data/spec/lutaml/xml/parse_dom_retention_spec.rb +34 -0
- data/spec/lutaml/xml/plan_fast_path_spec.rb +122 -14
- data/spec/lutaml/xml/plan_namespaced_serialize_spec.rb +157 -0
- data/spec/lutaml/xml/plan_ns_less_document_spec.rb +86 -0
- data/spec/lutaml/xml/plan_partition_spec.rb +1 -1
- data/spec/lutaml/xml/plan_type_ns_attribute_spec.rb +131 -0
- data/spec/lutaml/xml/plan_uri_exact_dispatch_spec.rb +287 -0
- data/spec/lutaml/xml/plan_walk_spec.rb +6 -6
- data/spec/lutaml/xml/plan_wildcard_any_spec.rb +96 -0
- data/spec/lutaml/yaml/yeptris_adapter_spec.rb +29 -0
- data/spec/person_spec.rb +8 -0
- metadata +48 -17
- data/lib/lutaml/xml/plan_compiler.rb +0 -332
- data/lib/lutaml/xml/plan_hydrator.rb +0 -367
- data/lib/lutaml/xml/plan_order.rb +0 -53
- data/lib/lutaml/xml/plan_serializer.rb +0 -252
- data/lib/lutaml/xml/plan_walk.rb +0 -30
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Xml
|
|
5
|
+
module Leptris
|
|
6
|
+
# The native emission lane of the plan fast path: instead of
|
|
7
|
+
# minting the output DOM node-by-node through the Ruby
|
|
8
|
+
# create_child chain, build a plan RESULT from ops
|
|
9
|
+
# (leptris_plan_result_build), render it through the engine's
|
|
10
|
+
# plan serializer, reparse the compact output, and write it with
|
|
11
|
+
# the engine's indented document writer. Both renderings are
|
|
12
|
+
# pure C; measured 2.4x faster than the DOM-mint loop on the
|
|
13
|
+
# 500-row audit at a modest allocation increase.
|
|
14
|
+
#
|
|
15
|
+
# Byte-parity with the DOM lane holds because both lanes end at
|
|
16
|
+
# the same document writer over structurally identical
|
|
17
|
+
# documents: the compact emission escapes exactly as the DOM
|
|
18
|
+
# text nodes do, and reparsing yields one text node per leaf,
|
|
19
|
+
# matching the minted tree. The reparse is an engine-internal
|
|
20
|
+
# cost, not a Ruby one.
|
|
21
|
+
#
|
|
22
|
+
# Emission is enabled for the provably static subset: trees with
|
|
23
|
+
# no namespace form anywhere (every spelling is the unwritten
|
|
24
|
+
# local name, nothing to declare, no instance-dependent
|
|
25
|
+
# prefix/default flips) and the non-ordered build() shapes
|
|
26
|
+
# (attributes, scalars, collections, nested models). Wildcard,
|
|
27
|
+
# raw, callback, and content rows and ordered models keep the
|
|
28
|
+
# DOM lane — their emission shapes are not expressible as ops
|
|
29
|
+
# (or not verified to be) and the DOM lane is byte-exact.
|
|
30
|
+
module NativeEmission
|
|
31
|
+
ROOT_ROW = 0xFFFF_FFFF
|
|
32
|
+
EMITTABLE_KINDS = %i[scalar collection nested].freeze
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def compatible?(plan)
|
|
36
|
+
!plan[:ordered] && tree_emittable?(plan[:tree])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Pre-order depth-first, children in row order — the same
|
|
40
|
+
# walk Descriptor.flatten_plan performs, so the plan_index
|
|
41
|
+
# counters here match the engine's flattened plans array.
|
|
42
|
+
def tree_emittable?(tree)
|
|
43
|
+
return false if tree[:ns]
|
|
44
|
+
return false if (tree[:attributes] || []).any? { |a| a[:ns].is_a?(Hash) }
|
|
45
|
+
|
|
46
|
+
(tree[:children] || []).all? do |row|
|
|
47
|
+
EMITTABLE_KINDS.include?(row[:kind]) &&
|
|
48
|
+
(row[:kind] != :nested || !row[:plan] || tree_emittable?(row[:plan]))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def emit(instance, plan, register)
|
|
53
|
+
ops = [{ kind: :element, row_index: ROOT_ROW }]
|
|
54
|
+
emit_element(ops, instance, plan, register, 0)
|
|
55
|
+
ops << { kind: :end }
|
|
56
|
+
compact = plan[:descriptor].serialize(
|
|
57
|
+
plan[:descriptor].build_result(ops),
|
|
58
|
+
)
|
|
59
|
+
::Leptris::XML.parse(compact).to_xml(indent: 2, no_decl: true)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# plan_index: this plan's flattened index. The engine's
|
|
65
|
+
# flatten_plan pushes each nested row's child plan exactly
|
|
66
|
+
# once — collection members share that slot — so child flat
|
|
67
|
+
# indices come from a static per-plan layout (relative to
|
|
68
|
+
# this plan's index), never from a runtime counter.
|
|
69
|
+
def emit_element(ops, instance, plan, register, plan_index)
|
|
70
|
+
my_index = plan_index
|
|
71
|
+
layout = flat_layout_for(plan)
|
|
72
|
+
|
|
73
|
+
emit_attributes(ops, instance, plan, my_index, register)
|
|
74
|
+
|
|
75
|
+
plan[:rows].each_with_index do |(rule, attr, kind, _spelling, delegate), row_index|
|
|
76
|
+
value = value_of(instance, attr, delegate)
|
|
77
|
+
next unless rule.render?(value, instance)
|
|
78
|
+
|
|
79
|
+
case kind
|
|
80
|
+
when :scalar
|
|
81
|
+
# Multi-capture parity: an attribute that collected
|
|
82
|
+
# several occurrences serializes one element per item,
|
|
83
|
+
# as the DOM lane does.
|
|
84
|
+
if value.is_a?(::Array)
|
|
85
|
+
value.each do |item|
|
|
86
|
+
ops << scalar_op(my_index, row_index, attr, item, register)
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
ops << scalar_op(my_index, row_index, attr, value, register)
|
|
90
|
+
end
|
|
91
|
+
when :collection_native, :collection_cb
|
|
92
|
+
Array(value).each do |item|
|
|
93
|
+
ops << scalar_op(my_index, row_index, attr, item, register)
|
|
94
|
+
end
|
|
95
|
+
when :nested
|
|
96
|
+
_tree, child_rel = layout[row_index]
|
|
97
|
+
next unless child_rel
|
|
98
|
+
|
|
99
|
+
Array(value).each do |item|
|
|
100
|
+
child_plan = nested_plans_for(plan)[[item.class, rule.name.to_s]] ||
|
|
101
|
+
(nested_plans_for(plan)[[item.class, rule.name.to_s]] =
|
|
102
|
+
PlanCompiler.compile(item.class, register))
|
|
103
|
+
ops << { kind: :element, plan_index: my_index,
|
|
104
|
+
row_index: row_index }
|
|
105
|
+
emit_element(ops, item, child_plan,
|
|
106
|
+
register, my_index + child_rel)
|
|
107
|
+
ops << { kind: :end }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# {nested row_index => [child tree, relative flat index]} —
|
|
114
|
+
# children of this plan occupy slots my_index+1 .. in row
|
|
115
|
+
# order, each nested row's subtree sized once.
|
|
116
|
+
def flat_layout_for(plan)
|
|
117
|
+
plan[:flat_layout] ||= begin
|
|
118
|
+
layout = {}
|
|
119
|
+
rel = 1
|
|
120
|
+
(plan[:tree][:children] || []).each_with_index do |row, i|
|
|
121
|
+
next unless row[:kind] == :nested && row[:plan]
|
|
122
|
+
|
|
123
|
+
layout[i] = [row[:plan], rel]
|
|
124
|
+
rel += subtree_size(row[:plan])
|
|
125
|
+
end
|
|
126
|
+
layout
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def subtree_size(tree)
|
|
131
|
+
1 + (tree[:children] || []).sum do |row|
|
|
132
|
+
row[:kind] == :nested && row[:plan] ? subtree_size(row[:plan]) : 0
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def emit_attributes(ops, instance, plan, plan_index, register)
|
|
137
|
+
recorded = instance.attribute_order
|
|
138
|
+
attr_rows = plan[:attr_rows]
|
|
139
|
+
if recorded && !recorded.empty?
|
|
140
|
+
by_name = {}
|
|
141
|
+
attr_rows.each { |rule, attr, _sp, delegate| by_name[rule.name.to_s] = [rule, attr, delegate] }
|
|
142
|
+
names = recorded | attr_rows.map { |rule, _a, _s, _d| rule.name.to_s }
|
|
143
|
+
else
|
|
144
|
+
names = attr_rows.map { |rule, _a, _s, _d| rule.name.to_s }
|
|
145
|
+
by_name = {}
|
|
146
|
+
attr_rows.each { |rule, attr, _sp, delegate| by_name[rule.name.to_s] = [rule, attr, delegate] }
|
|
147
|
+
end
|
|
148
|
+
tree_attrs = plan[:tree][:attributes] || []
|
|
149
|
+
names.each do |name|
|
|
150
|
+
entry = by_name[name]
|
|
151
|
+
next unless entry
|
|
152
|
+
|
|
153
|
+
_rule, attr, delegate = entry
|
|
154
|
+
value = value_of(instance, attr, delegate)
|
|
155
|
+
next if value.nil?
|
|
156
|
+
|
|
157
|
+
row_index = tree_attrs.index { |a| a[:name] == name }
|
|
158
|
+
next unless row_index
|
|
159
|
+
|
|
160
|
+
ops << { kind: :attr, plan_index: plan_index,
|
|
161
|
+
row_index: row_index, name: name,
|
|
162
|
+
value: attr.serialize(value, :xml, register).to_s }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def scalar_op(plan_index, row_index, attr, value, register)
|
|
167
|
+
{ kind: :scalar, plan_index: plan_index, row_index: row_index,
|
|
168
|
+
value: attr.serialize(value, :xml, register).to_s }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def value_of(instance, attr, delegate)
|
|
172
|
+
if delegate
|
|
173
|
+
target = instance.public_send(delegate)
|
|
174
|
+
target&.public_send(attr.name)
|
|
175
|
+
else
|
|
176
|
+
instance.public_send(attr.name)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def nested_plans_for(plan)
|
|
181
|
+
plan[:nested_plans] ||= {}
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|