lutaml-xsd 1.2.96 → 1.2.97
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: 74cc0c3c59a0cb1a80968d340ccf4d8be8b7d9ce9d1257c7e59044ebd85bc5b0
|
|
4
|
+
data.tar.gz: 453526a2baaeb84186acb8f3d84b573ddaeb988af120acf601bd2e621cd159e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67c01245e71827ab4682e6dda886b296586d96960b5e814b1b1e9ccbdee53d4fc33ee95ed705b90730e636f0f2c0ea5a7f325809072a7e0a0bd79d0928b91a79
|
|
7
|
+
data.tar.gz: 3246ef0bfd177d2019fa3fd08b01409647d2d2d71c4bb8757e108a55c9374f72684f258e46419bece176a03f94a24a3c44c11ec484b38f9fe60e4114099f022e
|
data/TODO.conformance.md
CHANGED
|
@@ -525,6 +525,21 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
|
|
|
525
525
|
cases (addB059/196c/197c, attO009/011) — filed moxml#529. THE
|
|
526
526
|
STACK IS PINNED BACK to the known-good 1.9.406.0/0.6.2/0.8.161
|
|
527
527
|
(98.28% restored, zero regressions vs the 406 baseline). The
|
|
528
|
+
- IDREF-RESOLUTION ROUND (2026-11-07): +1 IC (idZ012; suite
|
|
529
|
+
4620->4621 = 98.40%, zero regressions, rspec green). Every
|
|
530
|
+
IDREF/IDREFS value must match an ID in the document (XSD 1.0
|
|
531
|
+
§3.3.3) — the check did not exist at all. IdrefResolutionRule
|
|
532
|
+
(identity category, root-scoped two-phase scan beside
|
|
533
|
+
IdUniquenessRule) collects every document ID (element text +
|
|
534
|
+
attributes), then checks IDREF/IDREFS values from instance
|
|
535
|
+
attributes, element text, AND the declaration's fixed/default
|
|
536
|
+
supply when the instance omits the attribute — a defaulted IDREFS
|
|
537
|
+
still references (idZ012: drefs type="xs:IDREFS" default="abc"
|
|
538
|
+
against a document with no ID). IDREFS tokenizes; every token must
|
|
539
|
+
resolve; the two-phase order makes forward references valid.
|
|
540
|
+
Remaining IC singleton: idZ015 (a skip-wildcard-governed attribute
|
|
541
|
+
supplies no IC field value).
|
|
542
|
+
|
|
528
543
|
407 wins ride again when #47 and #529 land. NET: no score
|
|
529
544
|
- ID-VALUE-CONSTRAINT ROUND (2026-11-07): +1 IC (idZ011; suite
|
|
530
545
|
4619->4620 = 98.38%, zero regressions, rspec green). Value
|
|
@@ -8,6 +8,7 @@ require_relative "rules/occurrence_validation_rule"
|
|
|
8
8
|
require_relative "rules/content_model_validation_rule"
|
|
9
9
|
require_relative "rules/identity_constraints_rule"
|
|
10
10
|
require_relative "rules/id_uniqueness_rule"
|
|
11
|
+
require_relative "rules/idref_resolution_rule"
|
|
11
12
|
|
|
12
13
|
module Lutaml
|
|
13
14
|
module Xsd
|
|
@@ -63,6 +64,7 @@ module Lutaml
|
|
|
63
64
|
if config.feature_enabled?(:validate_identity_constraints)
|
|
64
65
|
registry.register(Rules::IdentityConstraintsRule.new)
|
|
65
66
|
registry.register(Rules::IdUniquenessRule.new)
|
|
67
|
+
registry.register(Rules::IdrefResolutionRule.new)
|
|
66
68
|
end
|
|
67
69
|
registry
|
|
68
70
|
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../validation_rule"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Xsd
|
|
7
|
+
module Validation
|
|
8
|
+
module Rules
|
|
9
|
+
# IdrefResolutionRule enforces built-in xs:IDREF(S) resolution
|
|
10
|
+
#
|
|
11
|
+
# XSD 1.0 §3.3.3: every IDREF/IDREFS value must match the value
|
|
12
|
+
# of some ID in the document. Values come from instance
|
|
13
|
+
# attributes, element text typed IDREF(S), AND a declaration's
|
|
14
|
+
# fixed/default supply when the instance omits the attribute —
|
|
15
|
+
# a defaulted IDREFS still references (idZ012: drefs
|
|
16
|
+
# type="xs:IDREFS" default="abc" against a document declaring
|
|
17
|
+
# no ID). IDREFS values tokenize on whitespace; every token
|
|
18
|
+
# must resolve. The scan is two-phase (all IDs first, then all
|
|
19
|
+
# references), so forward references resolve.
|
|
20
|
+
class IdrefResolutionRule < ValidationRule
|
|
21
|
+
def category
|
|
22
|
+
:identity
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def description
|
|
26
|
+
"Validates built-in xs:IDREF(S) values resolve to document IDs"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate(xml_element, schema_element, collector,
|
|
30
|
+
repository: nil)
|
|
31
|
+
return unless repository
|
|
32
|
+
return unless
|
|
33
|
+
schema_element.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
34
|
+
|
|
35
|
+
# The walk invokes the rule per element and per category;
|
|
36
|
+
# the document is scanned exactly once, from the first
|
|
37
|
+
# (root) invocation — element wrappers are not
|
|
38
|
+
# identity-stable across passes.
|
|
39
|
+
return if @scanned
|
|
40
|
+
|
|
41
|
+
@scanned = true
|
|
42
|
+
ids = collect_ids(xml_element, repository)
|
|
43
|
+
check_references(xml_element, ids, collector, repository)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Phase 1: every ID value in the document (element text and
|
|
49
|
+
# attributes). Mirrors IdUniquenessRule's walk — IDs come
|
|
50
|
+
# from instances only; a declaration cannot supply an ID that
|
|
51
|
+
# the document does not contain.
|
|
52
|
+
#
|
|
53
|
+
# @return [Set<String>]
|
|
54
|
+
def collect_ids(element, repository)
|
|
55
|
+
ids = []
|
|
56
|
+
declaration = repository.resolve(element.name,
|
|
57
|
+
element.namespace_uri)
|
|
58
|
+
if declaration
|
|
59
|
+
collect_element_ids(element, declaration, ids, repository)
|
|
60
|
+
collect_attribute_ids(element, declaration, ids, repository)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
element.children.each do |child|
|
|
64
|
+
ids.concat(collect_ids(child, repository))
|
|
65
|
+
end
|
|
66
|
+
ids.compact
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [void]
|
|
70
|
+
def collect_element_ids(element, declaration, ids, repository)
|
|
71
|
+
return unless builtin_typed?(declaration, repository, "ID")
|
|
72
|
+
|
|
73
|
+
# ID/IDREF(S) collapse whitespace (token derivation) — the
|
|
74
|
+
# value space is the collapsed form, never the raw lexical
|
|
75
|
+
# (dtZ107447: " John " both sides).
|
|
76
|
+
ids.concat(element.text_content.to_s.split)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [void]
|
|
80
|
+
def collect_attribute_ids(element, declaration, ids, repository)
|
|
81
|
+
typed_attributes(declaration, repository, "ID").each do |attr|
|
|
82
|
+
instance = element.attributes.find do |candidate|
|
|
83
|
+
candidate.name == attr.name
|
|
84
|
+
end
|
|
85
|
+
ids.concat(instance.value.to_s.split) if instance
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Phase 2: every IDREF(S) value the document carries or its
|
|
90
|
+
# declarations SUPPLY, each token matched against the IDs.
|
|
91
|
+
#
|
|
92
|
+
# @return [void]
|
|
93
|
+
def check_references(element, ids, collector, repository)
|
|
94
|
+
declaration = repository.resolve(element.name,
|
|
95
|
+
element.namespace_uri)
|
|
96
|
+
if declaration
|
|
97
|
+
check_element_reference(element, declaration, ids, collector,
|
|
98
|
+
repository)
|
|
99
|
+
check_attribute_references(element, declaration, ids,
|
|
100
|
+
collector, repository)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
element.children.each do |child|
|
|
104
|
+
check_references(child, ids, collector, repository)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [void]
|
|
109
|
+
def check_element_reference(element, declaration, ids, collector,
|
|
110
|
+
repository)
|
|
111
|
+
return unless
|
|
112
|
+
builtin_typed?(declaration, repository, "IDREF") ||
|
|
113
|
+
builtin_typed?(declaration, repository, "IDREFS")
|
|
114
|
+
|
|
115
|
+
reference_values(element.text_content.to_s).each do |value|
|
|
116
|
+
next if ids.include?(value)
|
|
117
|
+
|
|
118
|
+
report_unresolved(value, element, collector, "element")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Instance values, then the declaration's fixed/default supply
|
|
123
|
+
# for ABSENT attributes — a defaulted IDREFS still references
|
|
124
|
+
# (idZ012).
|
|
125
|
+
#
|
|
126
|
+
# @return [void]
|
|
127
|
+
def check_attribute_references(element, declaration, ids, collector,
|
|
128
|
+
repository)
|
|
129
|
+
typed_attributes(declaration, repository, "IDREF").each do |attr|
|
|
130
|
+
report_tokens(element, attr, ids, collector,
|
|
131
|
+
instance_value(element, attr))
|
|
132
|
+
end
|
|
133
|
+
typed_attributes(declaration, repository, "IDREFS").each do |attr|
|
|
134
|
+
report_tokens(element, attr, ids, collector,
|
|
135
|
+
instance_value(element, attr))
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [String, nil]
|
|
140
|
+
def instance_value(element, attr)
|
|
141
|
+
instance = element.attributes.find do |candidate|
|
|
142
|
+
candidate.name == attr.name
|
|
143
|
+
end
|
|
144
|
+
instance&.value || attr.fixed || attr.default
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @return [void]
|
|
148
|
+
def report_tokens(element, attr, ids, collector, supplied)
|
|
149
|
+
reference_values(supplied).each do |value|
|
|
150
|
+
next if ids.include?(value)
|
|
151
|
+
|
|
152
|
+
report_unresolved(value, element, collector,
|
|
153
|
+
"attribute '#{attr.name}'")
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# IDREF is a single value; IDREFS tokenizes on whitespace.
|
|
158
|
+
#
|
|
159
|
+
# @return [Array<String>]
|
|
160
|
+
def reference_values(supplied)
|
|
161
|
+
supplied.to_s.strip.empty? ? [] : supplied.to_s.split
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @return [void]
|
|
165
|
+
def report_unresolved(value, xml_element, collector, subject)
|
|
166
|
+
report_error(
|
|
167
|
+
collector,
|
|
168
|
+
code: "idref_violation",
|
|
169
|
+
message: "#{subject} has IDREF value '#{value}' that " \
|
|
170
|
+
"does not match any ID in the document",
|
|
171
|
+
location: xml_element.xpath,
|
|
172
|
+
context: { value: value },
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Whether the declaration's type is the named builtin — a
|
|
177
|
+
# direct reference or an inline restriction of it.
|
|
178
|
+
#
|
|
179
|
+
# @return [Boolean]
|
|
180
|
+
def builtin_typed?(declaration, _repository, name)
|
|
181
|
+
builtin_reference?(declaration.type.to_s, name) ||
|
|
182
|
+
builtin_reference?(
|
|
183
|
+
declaration.simple_type&.restriction&.base.to_s, name
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @return [Boolean]
|
|
188
|
+
def builtin_reference?(reference, name)
|
|
189
|
+
parts = reference.split(":")
|
|
190
|
+
parts.size == 2 && %w[xs xsd].include?(parts.first) &&
|
|
191
|
+
parts.last == name
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# The declaration's own attribute particles typed the builtin:
|
|
195
|
+
# direct attributes, refs resolved to their declarations, and
|
|
196
|
+
# attributeGroup members.
|
|
197
|
+
#
|
|
198
|
+
# @return [Array<Lutaml::Xml::Schema::Xsd::Attribute>]
|
|
199
|
+
def typed_attributes(schema_element, repository, name)
|
|
200
|
+
type = schema_element.complex_type ||
|
|
201
|
+
repository.resolve_type(schema_element)
|
|
202
|
+
return [] unless type.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType)
|
|
203
|
+
|
|
204
|
+
particles = Array(type.attribute)
|
|
205
|
+
Array(type.attribute_group).each do |group|
|
|
206
|
+
declaration = repository.resolve_attribute_group(
|
|
207
|
+
(group.ref || group.name).to_s.split(":").last,
|
|
208
|
+
)
|
|
209
|
+
particles.concat(Array(declaration&.attribute))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
particles.filter_map do |particle|
|
|
213
|
+
resolved = particle
|
|
214
|
+
unless particle.ref.to_s.empty?
|
|
215
|
+
resolved = repository.resolve_attribute(
|
|
216
|
+
particle.ref.split(":").last, nil
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
next nil unless resolved && builtin_typed?(resolved, repository,
|
|
220
|
+
name)
|
|
221
|
+
|
|
222
|
+
resolved
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
data/lib/lutaml/xsd/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-xsd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.97
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -578,6 +578,7 @@ files:
|
|
|
578
578
|
- lib/lutaml/xsd/validation/rules/element_structure_rule.rb
|
|
579
579
|
- lib/lutaml/xsd/validation/rules/id_uniqueness_rule.rb
|
|
580
580
|
- lib/lutaml/xsd/validation/rules/identity_constraints_rule.rb
|
|
581
|
+
- lib/lutaml/xsd/validation/rules/idref_resolution_rule.rb
|
|
581
582
|
- lib/lutaml/xsd/validation/rules/occurrence_validation_rule.rb
|
|
582
583
|
- lib/lutaml/xsd/validation/rules/type_validation_rule.rb
|
|
583
584
|
- lib/lutaml/xsd/validation/schema_definition_resolver.rb
|