lutaml-xsd 1.2.95 → 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 +4 -4
- data/TODO.conformance.md +33 -0
- data/lib/lutaml/xsd/validation/rule_registry.rb +2 -0
- data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +74 -0
- data/lib/lutaml/xsd/validation/rules/idref_resolution_rule.rb +229 -0
- data/lib/lutaml/xsd/version.rb +1 -1
- metadata +2 -1
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,7 +525,40 @@ 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
|
|
544
|
+
- ID-VALUE-CONSTRAINT ROUND (2026-11-07): +1 IC (idZ011; suite
|
|
545
|
+
4619->4620 = 98.38%, zero regressions, rspec green). Value
|
|
546
|
+
constraints (fixed/default) are FORBIDDEN on attribute declarations
|
|
547
|
+
typed xs:ID or derived from it (XSD 1.0 §3.2.6) — the schema is
|
|
548
|
+
uncompilable, so every instance reached is invalid regardless of
|
|
549
|
+
instance use. The attribute rule's declaration-scoped step 1c
|
|
550
|
+
checks the builtin-prefixed reference, ref-resolved declarations,
|
|
551
|
+
and the inline/named restriction base chain (depth 6); the
|
|
552
|
+
idZ011_a companion (xs:STRING default) stays clean. Family probes:
|
|
553
|
+
Attribute 113/113 clean, AttributeGroup 14 = the parked moxml#522
|
|
554
|
+
set unchanged. PROBE LESSON (repeat): single-process family probes
|
|
555
|
+
CRAWL after ~50 engine-heavy cases (engine-state accumulation;
|
|
556
|
+
enumeration order is unstable so the hang point moves) — the case
|
|
557
|
+
runs alone in <1s; batched methodology or the full measure is the
|
|
558
|
+
only reliable family signal at this scale. Remaining IC
|
|
559
|
+
singletons: idZ012 (IDREFS default references no ID), idZ015 (a
|
|
560
|
+
skip-wildcard-governed attribute supplies no IC field value).
|
|
561
|
+
|
|
529
562
|
change; the pin protects the register.
|
|
530
563
|
- NON-SIMPLE-FIELD ROUND (2026-11-07): +2 IC (idK012, idZ010; suite
|
|
531
564
|
4617->4619 = 98.36%, zero regressions, rspec green). An identity
|
|
@@ -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
|
|
@@ -106,6 +106,13 @@ module Lutaml
|
|
|
106
106
|
xml_attributes, collector,
|
|
107
107
|
wildcards: wildcards)
|
|
108
108
|
|
|
109
|
+
# Step 1c: an xs:ID declaration cannot carry a value
|
|
110
|
+
# constraint — the schema itself is uncompilable, so the
|
|
111
|
+
# instance is invalid wherever the declaration is reached
|
|
112
|
+
# (XSD 1.0 §3.2.6, idZ011).
|
|
113
|
+
validate_id_value_constraints(schema_attributes, collector,
|
|
114
|
+
xml_element, repository)
|
|
115
|
+
|
|
109
116
|
# Step 2: Validate attribute values
|
|
110
117
|
validate_attribute_values(xml_element, resolved_attributes,
|
|
111
118
|
xml_attributes, collector,
|
|
@@ -436,6 +443,73 @@ module Lutaml
|
|
|
436
443
|
end
|
|
437
444
|
end
|
|
438
445
|
|
|
446
|
+
# An xs:ID declaration with fixed or default is an illegal
|
|
447
|
+
# value constraint (XSD 1.0 §3.2.6): the type definition is
|
|
448
|
+
# or is derived from xs:ID. Declaration-scoped — fires on the
|
|
449
|
+
# element's type regardless of instance use (idZ011).
|
|
450
|
+
#
|
|
451
|
+
# @return [void]
|
|
452
|
+
def validate_id_value_constraints(schema_attrs, collector,
|
|
453
|
+
xml_element, repository)
|
|
454
|
+
schema_attrs.each do |schema_attr|
|
|
455
|
+
next if schema_attr.fixed.to_s.empty? &&
|
|
456
|
+
schema_attr.default.to_s.empty?
|
|
457
|
+
next unless id_typed_attribute?(schema_attr, repository)
|
|
458
|
+
|
|
459
|
+
report_error(
|
|
460
|
+
collector,
|
|
461
|
+
code: "id_value_constraint_violation",
|
|
462
|
+
message: "Attribute '#{schema_attr.name}' is typed " \
|
|
463
|
+
"xs:ID (or derived from it) and cannot carry " \
|
|
464
|
+
"a fixed or default value constraint",
|
|
465
|
+
location: xml_element.xpath,
|
|
466
|
+
context: { attribute: schema_attr.name },
|
|
467
|
+
)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Whether the declaration's type is xs:ID or derives from it:
|
|
472
|
+
# a builtin-prefixed reference, or a restriction base chain
|
|
473
|
+
# (inline or named) whose terminal is xs:ID.
|
|
474
|
+
#
|
|
475
|
+
# @return [Boolean]
|
|
476
|
+
def id_typed_attribute?(schema_attr, repository)
|
|
477
|
+
return true if builtin_id_type?(schema_attr.type)
|
|
478
|
+
|
|
479
|
+
ref = schema_attr.ref.to_s
|
|
480
|
+
if ref.match?(/\S/) && repository
|
|
481
|
+
resolved = repository.resolve_attribute(ref.split(":").last,
|
|
482
|
+
nil)
|
|
483
|
+
return true if resolved && builtin_id_type?(resolved.type)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
named = schema_attr.type.to_s.split(":").last
|
|
487
|
+
chain = schema_attr.simple_type ||
|
|
488
|
+
(named && repository&.resolve_named_type(named))
|
|
489
|
+
id_derived_chain?(chain, repository, 0)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# @return [Boolean]
|
|
493
|
+
def builtin_id_type?(reference)
|
|
494
|
+
parts = reference.to_s.split(":")
|
|
495
|
+
%w[xs xsd].include?(parts.first) && parts.last == "ID"
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# @param type [Lutaml::Xml::Schema::Xsd::SimpleType, nil]
|
|
499
|
+
# @return [Boolean]
|
|
500
|
+
def id_derived_chain?(type, repository, depth)
|
|
501
|
+
return false if depth > 6 || type.nil?
|
|
502
|
+
|
|
503
|
+
base = type.restriction&.base.to_s
|
|
504
|
+
return true if builtin_id_type?(base)
|
|
505
|
+
return false if base.empty?
|
|
506
|
+
|
|
507
|
+
id_derived_chain?(
|
|
508
|
+
repository&.resolve_named_type(base.split(":").last),
|
|
509
|
+
repository, depth + 1
|
|
510
|
+
)
|
|
511
|
+
end
|
|
512
|
+
|
|
439
513
|
# Check if attribute is required
|
|
440
514
|
#
|
|
441
515
|
# @param schema_attr [Lutaml::Xsd::Attribute] Schema attribute
|
|
@@ -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
|