lutaml-model 0.8.53 → 0.8.54

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: 15a10289cc84edab04063cf83dac41f6923500898b0b65e2c72db51421f8a2ad
4
- data.tar.gz: 4e6941d70effad354e4798013e27413eb0fd8d8b85643ca5621acb1e009b9247
3
+ metadata.gz: ffb19b7e87ec763167dc806fb0fd0b37a1ca292e7627333ba1238f06c3e5f17e
4
+ data.tar.gz: 340c6f96fb82f0d972ca654f548db51bdef12155bc94ae95e3eaf6eacf96af29
5
5
  SHA512:
6
- metadata.gz: 149a8dc04992492d770567285e43fdea303035e93a340554966cf0ff5c844cfb83c03023f214cbc1f50fe3f9e4a20bbce9e540656ed97092876c0b450924e4bd
7
- data.tar.gz: 82fc85442bef8b6f0014f03ac10373b03dd99b5be30631f45fbd2e924d02cc6baafbcd75fbb17249c648f9e8711609ba2d5d11494e80f9c6ab734d3cba517bf9
6
+ metadata.gz: d79e2b5b7fce4f6ba3115cdb190365b8a1fc1212922f00cabf2e9855fa85c2350808c0844f78c55015de4e8c8cf480cbdf5390e8afaa0a401f01ad6e2a1d93a3
7
+ data.tar.gz: c6a29c349be599ddfcbcb53087c17178de7de17aa1afa46fb5e2fc6eb2b3c4b0c0189b68a26aa77d0e2d561966c4001c0bd04abacf2d6e987d8846185b6add29
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.53"
5
+ VERSION = "0.8.54"
6
6
  end
7
7
  end
@@ -731,18 +731,29 @@ _effective_register)
731
731
  # each_value scan below is dead whenever the index exists.
732
732
  return nil if root_index && !candidates
733
733
 
734
- matched_attr = if candidates
735
- candidates.find do |attr|
736
- local_name_match?(attr, local_name, rule_uri,
737
- flexible_local)
738
- end
739
- else
740
- doc.root.attributes.each_value.find do |attr|
741
- local_name_match?(attr, local_name, rule_uri,
742
- flexible_local)
743
- end
744
- end
745
- matched_attr&.value
734
+ matched = if candidates
735
+ candidates.select do |attr|
736
+ local_name_match?(attr, local_name, rule_uri,
737
+ flexible_local)
738
+ end
739
+ else
740
+ doc.root.attributes.each_value.select do |attr|
741
+ local_name_match?(attr, local_name, rule_uri,
742
+ flexible_local)
743
+ end
744
+ end
745
+ # Attribute identity is (namespace URI, local name) — #841. The
746
+ # lenient probe is a recovery mechanism for the single
747
+ # undeclared-prefix case; several distinct matches are an
748
+ # ambiguity the document order must not arbitrate.
749
+ if matched.size > 1
750
+ identities = matched.map(&:namespaced_name).join(", ")
751
+ warn "[Lutaml::Model] ambiguous attribute for rule " \
752
+ "#{rule_name.inspect}: #{identities} all match by local " \
753
+ "name on <#{doc.root.unprefixed_name}>; no value bound"
754
+ return nil
755
+ end
756
+ matched.first&.value
746
757
  end
747
758
 
748
759
  # Namespace discipline of the lenient local-name fallback
@@ -385,11 +385,24 @@ module Lutaml
385
385
  ensure_attribute_index
386
386
 
387
387
  if attribute_name.is_a?(Array)
388
+ # Alias list: at most one spelling variant may be present.
389
+ # Several simultaneous matches are an authoring ambiguity —
390
+ # attribute identity is (namespace URI, local name) — #841 —
391
+ # and array order must not arbitrate.
392
+ found = nil
388
393
  attribute_name.each do |name|
389
394
  val = @attribute_index[name]
390
- return val unless val.nil?
395
+ next if val.nil?
396
+
397
+ if found
398
+ warn "[Lutaml::Model] ambiguous attribute aliases " \
399
+ "#{found[0].inspect} and #{name.inspect} both present " \
400
+ "on <#{unprefixed_name}>; no value bound"
401
+ return nil
402
+ end
403
+ found = [name, val]
391
404
  end
392
- nil
405
+ found&.last
393
406
  else
394
407
  @attribute_index[attribute_name]
395
408
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Attribute identity is (namespace URI, local name) — lutaml-model#841.
6
+ # Where matching previously resolved silently by order (document order
7
+ # in the lenient recovery, array order in alias lists), several
8
+ # distinct matches are an ambiguity: no value binds and the collision
9
+ # is surfaced on stderr.
10
+ module AttributeAmbiguitySurfaceSpec
11
+ # Lenient recovery: sole-claimant rule spelled URI:local while the
12
+ # document's prefixes stay undeclared (#754 recovery case).
13
+ class LenientProbe < Lutaml::Model::Serializable
14
+ attribute :ext, :string
15
+
16
+ xml do
17
+ element "probe"
18
+ map_attribute "urn:example:v:ext", to: :ext
19
+ end
20
+ end
21
+
22
+ # Alias list: spelling variants of one name — at most one may occur.
23
+ class AliasProbe < Lutaml::Model::Serializable
24
+ attribute :status, :string
25
+
26
+ xml do
27
+ element "probe"
28
+ map_attribute %w[status product-status], to: :status
29
+ end
30
+ end
31
+ end
32
+
33
+ RSpec.describe "Ambiguous attribute matches surface instead of resolving by order" do
34
+ describe "lenient local-name recovery" do
35
+ it "recovers the single undeclared-prefix attribute" do
36
+ probe = AttributeAmbiguitySurfaceSpec::LenientProbe.from_xml(
37
+ %(<probe v:ext="1"/>),
38
+ )
39
+ expect(probe.ext).to eq("1")
40
+ end
41
+
42
+ it "binds no value when several same-local attributes match" do
43
+ expect do
44
+ probe = AttributeAmbiguitySurfaceSpec::LenientProbe.from_xml(
45
+ %(<probe v:ext="1" w:ext="2"/>),
46
+ )
47
+ expect(probe.ext).to be_nil
48
+ end.to output(/ambiguous attribute/i).to_stderr
49
+ end
50
+
51
+ it "prefers the exact qualified match when the prefix IS declared" do
52
+ probe = AttributeAmbiguitySurfaceSpec::LenientProbe.from_xml(
53
+ %(<probe xmlns:v="urn:example:v" v:ext="1" w:ext="2"/>),
54
+ )
55
+ expect(probe.ext).to eq("1")
56
+ end
57
+ end
58
+
59
+ describe "alias lists" do
60
+ it "reads the single present alias" do
61
+ probe = AttributeAmbiguitySurfaceSpec::AliasProbe.from_xml(
62
+ %(<probe product-status="draft"/>),
63
+ )
64
+ expect(probe.status).to eq("draft")
65
+
66
+ probe = AttributeAmbiguitySurfaceSpec::AliasProbe.from_xml(
67
+ %(<probe status="open"/>),
68
+ )
69
+ expect(probe.status).to eq("open")
70
+ end
71
+
72
+ it "binds no value when two aliases are simultaneously present" do
73
+ expect do
74
+ probe = AttributeAmbiguitySurfaceSpec::AliasProbe.from_xml(
75
+ %(<probe status="open" product-status="draft"/>),
76
+ )
77
+ expect(probe.status).to be_nil
78
+ end.to output(/ambiguous attribute aliases/i).to_stderr
79
+ end
80
+ end
81
+ end
@@ -40,11 +40,19 @@ RSpec.describe "XmlElement performance guard specs" do
40
40
  expect(element.find_attribute_value("missing")).to be_nil
41
41
  end
42
42
 
43
- it "finds first match from array of names" do
44
- result = element.find_attribute_value(["missing1", "class", "id"])
43
+ it "finds the single present name from array of names" do
44
+ result = element.find_attribute_value(["missing1", "class", "missing2"])
45
45
  expect(result).to eq("foo")
46
46
  end
47
47
 
48
+ it "binds no value when two names in the array are both present" do
49
+ # Attribute identity is (namespace URI, local name) — #841;
50
+ # array order must not arbitrate an ambiguity.
51
+ expect do
52
+ expect(element.find_attribute_value(["class", "id"])).to be_nil
53
+ end.to output(/ambiguous attribute aliases/i).to_stderr
54
+ end
55
+
48
56
  it "returns nil when no name in array matches" do
49
57
  expect(element.find_attribute_value(["missing1", "missing2"])).to be_nil
50
58
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.53
4
+ version: 0.8.54
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-21 00:00:00.000000000 Z
11
+ date: 2026-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -1902,6 +1902,7 @@ files:
1902
1902
  - spec/lutaml/xml/adapter/ox_adapter_spec.rb
1903
1903
  - spec/lutaml/xml/adapter/rexml_adapter_spec.rb
1904
1904
  - spec/lutaml/xml/adapter/xml_namespace_spec.rb
1905
+ - spec/lutaml/xml/attribute_ambiguity_surface_spec.rb
1905
1906
  - spec/lutaml/xml/attribute_element_namespace_disjoint_spec.rb
1906
1907
  - spec/lutaml/xml/attribute_namespace_disjointness_spec.rb
1907
1908
  - spec/lutaml/xml/builder/builder_spec.rb