lutaml-model 0.8.85 → 0.8.86
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91e44ff1cc09c4f38dfe2a18d6d7bad7b70a7828ed6c590c5d808848c0c520eb
|
|
4
|
+
data.tar.gz: 679150748631c4dbb8063ca6d832028f41c47d9df33561f7f2833535acddb4b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5c248fcd779fdfa8652d69e8036c38c83b92068f764c56a505154efddee3270bee5cf3dc3cc9ca8f4ef3d056d37bba76cdb45f2e90b698e78478d3368a4d35b
|
|
7
|
+
data.tar.gz: d696297bd441cb8a1bc9b7b7adf18fa258e8708034e293c181178d91f58b19ad3ccab5e61689af91913b9318ab2120ded1973616e28d79fa741702e709ba2e07
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/xml/mapping.rb
CHANGED
|
@@ -59,6 +59,7 @@ module Lutaml
|
|
|
59
59
|
|
|
60
60
|
@elements = {}
|
|
61
61
|
@attributes = {}
|
|
62
|
+
@any_element_rule = nil
|
|
62
63
|
@element_sequence = []
|
|
63
64
|
@content_mapping = nil
|
|
64
65
|
@raw_mapping = nil
|
|
@@ -504,6 +505,26 @@ module Lutaml
|
|
|
504
505
|
# @return [String, nil] the type name
|
|
505
506
|
alias xsd_type type_name
|
|
506
507
|
|
|
508
|
+
# Catch-all element mapping: every child element with no
|
|
509
|
+
# explicit rule routes to one collection attribute. For
|
|
510
|
+
# parse-side consumption of dynamic vocabularies (semantic__
|
|
511
|
+
# trees); element_order preserves the original tag names.
|
|
512
|
+
# Serialization of a catch-all attribute emits the attribute's
|
|
513
|
+
# declared element name (not per-item originals).
|
|
514
|
+
def map_any_element(to:, render_nil: false, render_default: false)
|
|
515
|
+
validate!(nil, to, {}, render_nil, false, type: TYPES[:element])
|
|
516
|
+
@any_element_rule = MappingRule.new(
|
|
517
|
+
nil,
|
|
518
|
+
to: to,
|
|
519
|
+
render_nil: render_nil,
|
|
520
|
+
render_default: render_default,
|
|
521
|
+
)
|
|
522
|
+
@element_rules_index = nil if instance_variable_defined?(:@element_rules_index)
|
|
523
|
+
@any_element_rule
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
attr_reader :any_element_rule
|
|
527
|
+
|
|
507
528
|
def map_instances(to:, polymorphic: {})
|
|
508
529
|
map_element(to, to: to, polymorphic: polymorphic)
|
|
509
530
|
end
|
|
@@ -1342,9 +1363,9 @@ module Lutaml
|
|
|
1342
1363
|
elsif ["text", "#cdata-section"].include?(name.to_s) && type == "Text"
|
|
1343
1364
|
content_mapping
|
|
1344
1365
|
else
|
|
1345
|
-
candidates =
|
|
1346
|
-
|
|
1347
|
-
|
|
1366
|
+
candidates = element_rules_index[name.to_s]
|
|
1367
|
+
return @any_element_rule if candidates.nil? && @any_element_rule
|
|
1368
|
+
return nil if candidates.nil?
|
|
1348
1369
|
return candidates.first if namespace_uri.nil? || candidates.one?
|
|
1349
1370
|
|
|
1350
1371
|
candidates.find do |r|
|
|
@@ -1353,6 +1374,29 @@ module Lutaml
|
|
|
1353
1374
|
end
|
|
1354
1375
|
end
|
|
1355
1376
|
|
|
1377
|
+
# O(1) name -> rules lookup. Parsing consults this once per
|
|
1378
|
+
# child element; the linear scan over every rule made large
|
|
1379
|
+
# documents quadratic (155s on a 7MB vocabulary tree). Keyed on
|
|
1380
|
+
# the stable source hashes (@elements/@attributes mutate only at
|
|
1381
|
+
# DSL time), never on the per-call mappings array.
|
|
1382
|
+
def element_rules_index
|
|
1383
|
+
cache_key = [@elements.object_id, @attributes.object_id]
|
|
1384
|
+
cached = (@element_rules_index ||= {})
|
|
1385
|
+
idx = cached[cache_key]
|
|
1386
|
+
return idx if idx
|
|
1387
|
+
|
|
1388
|
+
idx = {}
|
|
1389
|
+
([content_mapping, raw_mapping].compact + @elements.values +
|
|
1390
|
+
@attributes.values).flatten.each do |rule|
|
|
1391
|
+
next if rule.is_a?(::Hash)
|
|
1392
|
+
|
|
1393
|
+
(idx[rule.name.to_s] ||= []) << rule
|
|
1394
|
+
end
|
|
1395
|
+
cached[cache_key] = idx
|
|
1396
|
+
idx
|
|
1397
|
+
end
|
|
1398
|
+
private :element_rules_index
|
|
1399
|
+
|
|
1356
1400
|
def find_by_to(to)
|
|
1357
1401
|
mappings.detect { |rule| rule.to.to_s == to.to_s }
|
|
1358
1402
|
end
|
|
@@ -263,6 +263,9 @@ module Lutaml
|
|
|
263
263
|
# Performance: Resolve xml_mapping once — used for both rules and metadata
|
|
264
264
|
xml_mapping = mappings_for(:xml, effective_register)
|
|
265
265
|
mappings = options[:mappings] || xml_mapping.mappings(effective_register)
|
|
266
|
+
# Catch-all rule (map_any_element): iterated like any other,
|
|
267
|
+
# claiming every child no explicit rule matches.
|
|
268
|
+
mappings += [xml_mapping.any_element_rule].compact if options[:mappings].nil?
|
|
266
269
|
default_namespace = options[:default_namespace]
|
|
267
270
|
ordered_option = options[:ordered]
|
|
268
271
|
mixed_content_option = options[:mixed_content]
|
|
@@ -361,7 +364,25 @@ module Lutaml
|
|
|
361
364
|
options
|
|
362
365
|
end
|
|
363
366
|
|
|
364
|
-
value = if rule.
|
|
367
|
+
value = if !rule.attribute? && rule.name.nil? &&
|
|
368
|
+
xml_mapping.any_element_rule.equal?(rule)
|
|
369
|
+
# map_any_element: every child element no explicit
|
|
370
|
+
# element rule claims, in document order. A name-less
|
|
371
|
+
# MappingRule is otherwise indistinguishable from a
|
|
372
|
+
# content rule (the dispatch consumed it as doc.text);
|
|
373
|
+
# name-derived lookup also cannot express this — it
|
|
374
|
+
# re-finds by name, losing order and double-picking.
|
|
375
|
+
# The pass walks the document children once; the
|
|
376
|
+
# normalize/cast pipeline below handles the rest.
|
|
377
|
+
claimed = Set.new(
|
|
378
|
+
xml_mapping.mappings(effective_register)
|
|
379
|
+
.select { |r| !r.attribute? && r.name }
|
|
380
|
+
.flat_map { |r| [r.name.to_s] },
|
|
381
|
+
)
|
|
382
|
+
doc.element_children.reject do |child|
|
|
383
|
+
claimed.include?(child.unprefixed_name)
|
|
384
|
+
end
|
|
385
|
+
elsif rule.raw_mapping?
|
|
365
386
|
scoped_raw_inner_xml(doc, xml_mapping)
|
|
366
387
|
elsif rule.content_mapping?
|
|
367
388
|
rule.cdata ? doc.cdata : doc.text
|
|
@@ -369,6 +390,8 @@ module Lutaml
|
|
|
369
390
|
group.size > 1 && attr&.collection?
|
|
370
391
|
# First rule of a #765 group: match every spelling of
|
|
371
392
|
# the group so the children merge in document order.
|
|
393
|
+
raise "GROUPED-BODY" if ENV["CATCHDBG"] && rule.name.nil?
|
|
394
|
+
|
|
372
395
|
if child_names_set &&
|
|
373
396
|
group.drop(1).none? do |s|
|
|
374
397
|
child_matches_rule?(s, child_names_set,
|
|
@@ -385,9 +408,11 @@ module Lutaml
|
|
|
385
408
|
value_for_rule(session, rule, new_opts, attr,
|
|
386
409
|
extra_names)
|
|
387
410
|
end
|
|
388
|
-
elsif child_names_set && !rule.attribute? &&
|
|
411
|
+
elsif child_names_set && !rule.attribute? && rule.name &&
|
|
389
412
|
!child_matches_rule?(rule, child_names_set,
|
|
390
413
|
default_namespace)
|
|
414
|
+
raise "FASTSKIP-BODY" if ENV["CATCHDBG"] && rule.name.nil?
|
|
415
|
+
|
|
391
416
|
# Pre-match: no child element matches this rule.
|
|
392
417
|
# Skip expensive value_for_rule, handle defaults inline.
|
|
393
418
|
# NOTE: do not "read the instance" here (to_value_for) —
|
|
@@ -400,6 +425,8 @@ module Lutaml
|
|
|
400
425
|
::Lutaml::Model::UninitializedClass.instance
|
|
401
426
|
end
|
|
402
427
|
else
|
|
428
|
+
raise "ELSE-BODY" if ENV["CATCHDBG"] && rule.name.nil?
|
|
429
|
+
|
|
403
430
|
# Performance: Pass cached attr to avoid recomputing attribute_for_rule
|
|
404
431
|
val = value_for_rule(session, rule, new_opts, attr)
|
|
405
432
|
|
|
@@ -24,6 +24,10 @@ module Lutaml
|
|
|
24
24
|
@instance_is_serialize ||= instance.is_a?(::Lutaml::Model::Serialize)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def mapping
|
|
28
|
+
@mapping ||= instance.class.mappings_for(:xml, register)
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
# (element, rule name) -> resolved attribute name / matched
|
|
28
32
|
# attribute VALUE for the lenient lookup paths. Both are pure
|
|
29
33
|
# functions of the element and the name; ISO-13849-shaped
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
class AnyElementNode < Lutaml::Model::Serializable
|
|
6
|
+
attribute :id, :string
|
|
7
|
+
attribute :children, "AnyElementNode", collection: true
|
|
8
|
+
|
|
9
|
+
xml do
|
|
10
|
+
element "root"
|
|
11
|
+
map_element "id", to: :id
|
|
12
|
+
map_any_element to: :children
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
RSpec.describe AnyElementNode do
|
|
17
|
+
it "routes unmapped child elements to the catch-all collection" do
|
|
18
|
+
doc = described_class.from_xml(<<~XML)
|
|
19
|
+
<root>
|
|
20
|
+
<id>r-1</id>
|
|
21
|
+
<semantic__p>hello</semantic__p>
|
|
22
|
+
<semantic__title>Nested</semantic__title>
|
|
23
|
+
<semantic__p><semantic__em>deep</semantic__em></semantic__p>
|
|
24
|
+
</root>
|
|
25
|
+
XML
|
|
26
|
+
|
|
27
|
+
expect(doc.id).to eq("r-1")
|
|
28
|
+
expect(doc.children.size).to eq(3)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "keeps explicit rules ahead of the catch-all" do
|
|
32
|
+
doc = described_class.from_xml("<root><id>x</id></root>")
|
|
33
|
+
expect(doc.id).to eq("x")
|
|
34
|
+
expect(doc.children).to be_empty
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "nests catch-all children recursively" do
|
|
38
|
+
doc = described_class.from_xml("<root><semantic__p><semantic__em>deep</semantic__em></semantic__p></root>")
|
|
39
|
+
expect(doc.children.first.children.size).to eq(1)
|
|
40
|
+
end
|
|
41
|
+
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.86
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -1697,6 +1697,7 @@ files:
|
|
|
1697
1697
|
- spec/lutaml/key_value/when_attribute_spec.rb
|
|
1698
1698
|
- spec/lutaml/model/absent_value_contracts_spec.rb
|
|
1699
1699
|
- spec/lutaml/model/adapter_resolver_configured_spec.rb
|
|
1700
|
+
- spec/lutaml/model/any_element_mapping_spec.rb
|
|
1700
1701
|
- spec/lutaml/model/attribute_apply_value_map_spec.rb
|
|
1701
1702
|
- spec/lutaml/model/attribute_collection_spec.rb
|
|
1702
1703
|
- spec/lutaml/model/attribute_default_cache_spec.rb
|