lutaml-xsd 1.2.82 → 1.2.84
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 +40 -2
- data/lib/lutaml/xsd/conformance/w3c_suite.rb +11 -2
- data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +15 -5
- data/lib/lutaml/xsd/validation/rules/content_model_validation_rule.rb +7 -2
- data/lib/lutaml/xsd/validation/schema_definition_resolver.rb +108 -33
- data/lib/lutaml/xsd/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ba83c4ddd3e8f05926b71bd53ed35e0aba700381e1ca5ed13c803ac2164412b
|
|
4
|
+
data.tar.gz: ae5e50d04a1a0230afbbdf904484601800b5772b2348c2b374feef2fa75403d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d52035a236b15bc9a063269dd5ee14389ec12d36619eb22cfcc85bbb9e98e17b2aedd55e41b6dbcbf7eb9e2f1797aaf83423b58d657e6e281befd52ab61c928
|
|
7
|
+
data.tar.gz: 6b35f257dbc8cadcd929212015294b407c2e5836c375e568a24bf71f05e41eb9823fbb97bf167f7bc289792e9a0f1d8f060088a7931594bc1b5d96aa99992a11
|
data/TODO.conformance.md
CHANGED
|
@@ -120,7 +120,7 @@ pinned commit) is measured via `rake conformance:w3c`:
|
|
|
120
120
|
|
|
121
121
|
| mode | pass-rate |
|
|
122
122
|
|---|---|
|
|
123
|
-
| pipeline |
|
|
123
|
+
| pipeline | 4588/4696 (97.7%) |
|
|
124
124
|
|
|
125
125
|
(2026-11-02, bundled-stack measurement with the escape,
|
|
126
126
|
hint-following, pattern-alternative, identity-crash, engine-set,
|
|
@@ -143,7 +143,8 @@ lockfile refresh) → 4411 (93.9%) → 4430 (94.3%) → 4435 (94.4%) →
|
|
|
143
143
|
(96.3%) → 4534 (96.6%) → 4540 (96.7%) → 4542 (96.7%) → 4546 (96.8%)
|
|
144
144
|
→ 4557 (97.0%) → 4565 (97.2%) → 4569 (97.3%) → 4562 (drift raw) →
|
|
145
145
|
4563 (97.17%, ref-namespace round) → 4573 (97.38%, group-content
|
|
146
|
-
round) → 4578 (97.49%, attribute-prohibition round)
|
|
146
|
+
round) → 4578 (97.49%, attribute-prohibition round) → 4585 (97.64%,
|
|
147
|
+
multi-document round) → 4588 (97.7%, type-binding round), +627 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
|
|
147
148
|
\w/\W/\s to their XSD value spaces) collapses Regex 120→69;
|
|
148
149
|
same-step pattern facets now behave as alternatives and
|
|
149
150
|
whitespace-only text reaches the facets intact (PR #200), Regex
|
|
@@ -470,6 +471,43 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
|
|
|
470
471
|
use-site fixed overlays the global declaration in the value check
|
|
471
472
|
(attLa005). Attribute remainder (4): attJ018 + attO009/011
|
|
472
473
|
(engine-silent — queue), attO006 (leptris-rs#25 attribute leg).
|
|
474
|
+
- MULTI-DOCUMENT ROUND (2026-11-03): +7 (Schema 12→9 — the schT3/T9/
|
|
475
|
+
T10 redefine trio; ComplexType 4→1, Particles 12→10, Additional
|
|
476
|
+
10→9, IC 7→6 — same-namespace collision pairs; suite 4578→4585 =
|
|
477
|
+
97.64%, zero regressions, rspec green). TWO defects: (a) the
|
|
478
|
+
conformance repository keyed documents by TARGET NAMESPACE, so two
|
|
479
|
+
schemaDocuments sharing a namespace silently overwrote each other
|
|
480
|
+
and only one survived (schT3's redefine pair, both ns-a); a
|
|
481
|
+
same-namespace collision now suffixes the key instead of dropping
|
|
482
|
+
the document — every other case keeps the production one-doc-per-
|
|
483
|
+
namespace semantics (a naive path-key for everything cost 25
|
|
484
|
+
regressions via the unique-name fallback and first-match type
|
|
485
|
+
lookup, caught and reverted in-round). (b) resolve_named_type now
|
|
486
|
+
falls back to the parsed xs:redefine target documents: types living
|
|
487
|
+
only in the target (schT3 b-ct/b-st) resolve for plain references;
|
|
488
|
+
names a redefine BODY redefines stay OFF that path (the body is the
|
|
489
|
+
effective type and is not resolvable here — idF/idG/idH keep the
|
|
490
|
+
lenient path). KNOWN GAPS left open: schA1/schQ1/schD10/schF2
|
|
491
|
+
(hint-composition with same-local-name ct-A across namespaces —
|
|
492
|
+
per-namespace type binding), full type-redefine body semantics
|
|
493
|
+
(the body as effective type with the self-base hook — a
|
|
494
|
+
lutaml-model Redefine model shape study). Remaining Schema 9:
|
|
495
|
+
schA1/Q1/D10/F2 (ours), schD7/G8 (lutaml-model#1087),
|
|
496
|
+
schF1/G2/G7/P2 (moxml#510 composed bindings).
|
|
497
|
+
- TYPE-BINDING ROUND (2026-11-03): +3 (Schema 9→8 — schQ1/schF2
|
|
498
|
+
flip; Particles 12→8 cumulatively with the collision round; suite
|
|
499
|
+
4585→4588 = 97.7%, zero regressions, rspec green). Same-local-name
|
|
500
|
+
types across a composition (three documents each declaring ct-A in
|
|
501
|
+
ns-a/ns-b/no-namespace) resolved to the FIRST candidate; the
|
|
502
|
+
resolver now keeps every candidate in document order and prefers
|
|
503
|
+
the one declared in the OWNING declaration's namespace (type
|
|
504
|
+
namespaces indexed at index time), with the no-namespace owner
|
|
505
|
+
preferring the no-namespace candidate; xsi:type overrides and
|
|
506
|
+
extension-base walks thread the deriving type namespace through.
|
|
507
|
+
STILL OPEN: schA1/schD10 (the hinted composition's CHILD value
|
|
508
|
+
checks bind the no-ns int ct-A to the ns-b boolean children —
|
|
509
|
+
'true' vs xsd:int — the per-child type plumbing under hint
|
|
510
|
+
layering), full type-redefine body semantics.
|
|
473
511
|
- REGEX SEMANTICS ROUND (2026-10-31, PR #208): \w/\W now carry their
|
|
474
512
|
Appendix F complements (\w = [^\p{P}\p{Z}\p{C}], class interiors
|
|
475
513
|
via the exact positive partition L/M/N/S), \I/\C complements added
|
|
@@ -154,8 +154,17 @@ module Lutaml
|
|
|
154
154
|
test_case.schema_paths.each do |path|
|
|
155
155
|
schema = Lutaml::Xml::Schema::Xsd.parse(File.read(path),
|
|
156
156
|
location: path)
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
# Keyed by target namespace like production, EXCEPT that a
|
|
158
|
+
# same-namespace collision suffixes the key instead of
|
|
159
|
+
# dropping the document (schT3's redefine pair both declare
|
|
160
|
+
# ns-a). The resolver iterates values; keys only need to
|
|
161
|
+
# keep every document present.
|
|
162
|
+
key = if schema.target_namespace.to_s.match?(/\S/)
|
|
163
|
+
schema.target_namespace
|
|
164
|
+
else
|
|
165
|
+
path
|
|
166
|
+
end
|
|
167
|
+
key = "#{key}##{path}" while schemas.key?(key)
|
|
159
168
|
schemas[key] = schema
|
|
160
169
|
locations[path] = schema
|
|
161
170
|
end
|
|
@@ -222,7 +222,11 @@ module Lutaml
|
|
|
222
222
|
# Base attributes flow through both derivations — a
|
|
223
223
|
# restriction narrows them but cannot drop them (XSD 1.0
|
|
224
224
|
# §3.4.2).
|
|
225
|
-
|
|
225
|
+
if part
|
|
226
|
+
attributes.concat(
|
|
227
|
+
inherited_attributes(part, repository),
|
|
228
|
+
)
|
|
229
|
+
end
|
|
226
230
|
end
|
|
227
231
|
|
|
228
232
|
# Handle simple content
|
|
@@ -234,7 +238,11 @@ module Lutaml
|
|
|
234
238
|
)
|
|
235
239
|
part = Array(schema_def.simple_content.extension).first ||
|
|
236
240
|
Array(schema_def.simple_content.restriction).first
|
|
237
|
-
|
|
241
|
+
if part
|
|
242
|
+
attributes.concat(
|
|
243
|
+
inherited_attributes(part, repository),
|
|
244
|
+
)
|
|
245
|
+
end
|
|
238
246
|
end
|
|
239
247
|
|
|
240
248
|
override_base_prohibitions(schema_def, attributes)
|
|
@@ -267,11 +275,13 @@ module Lutaml
|
|
|
267
275
|
end
|
|
268
276
|
|
|
269
277
|
# The base type's attribute set flows into the derived type
|
|
270
|
-
# through extension and restriction alike.
|
|
278
|
+
# through extension and restriction alike. The base resolves
|
|
279
|
+
# through the derivation so a redefine body's own base hooks
|
|
280
|
+
# to the original definition.
|
|
271
281
|
#
|
|
272
282
|
# @return [Array<Lutaml::Xml::Schema::Xsd::Attribute>]
|
|
273
|
-
def inherited_attributes(
|
|
274
|
-
local =
|
|
283
|
+
def inherited_attributes(part, repository)
|
|
284
|
+
local = part.base.to_s.split(":").last
|
|
275
285
|
return [] if local.to_s.empty?
|
|
276
286
|
|
|
277
287
|
base = repository&.resolve_named_type(local)
|
|
@@ -247,7 +247,9 @@ module Lutaml
|
|
|
247
247
|
xsi = repository && xsi_attribute(xml_element, "type")
|
|
248
248
|
if xsi&.value.to_s.match?(/\S/)
|
|
249
249
|
local = xsi.value.to_s.split(":").last
|
|
250
|
-
named = repository.resolve_named_type(
|
|
250
|
+
named = repository.resolve_named_type(
|
|
251
|
+
local, repository.target_namespace_for(schema_def)
|
|
252
|
+
)
|
|
251
253
|
return nil unless named
|
|
252
254
|
|
|
253
255
|
return type_particles(named, repository, {}.compare_by_identity)
|
|
@@ -302,7 +304,10 @@ module Lutaml
|
|
|
302
304
|
restriction = Array(content.restriction).first
|
|
303
305
|
if extension
|
|
304
306
|
base_local = extension.base.to_s.split(":").last
|
|
305
|
-
base_type = base_local &&
|
|
307
|
+
base_type = base_local &&
|
|
308
|
+
repository&.resolve_named_type(
|
|
309
|
+
base_local, repository&.target_namespace_for(type)
|
|
310
|
+
)
|
|
306
311
|
particles = type_particles(base_type, repository, seen) +
|
|
307
312
|
particles
|
|
308
313
|
COMPLEX_PARTICLE_KINDS.each do |kind|
|
|
@@ -50,6 +50,14 @@ module Lutaml
|
|
|
50
50
|
@attributes = Hash.new { |hash, key| hash[key] = [] }
|
|
51
51
|
@attribute_groups = {}
|
|
52
52
|
@redefined_originals = {}
|
|
53
|
+
# Parsed xs:redefine target documents: their named types back
|
|
54
|
+
# the redefined bodies (schT3: b-ct/b-st live in the target,
|
|
55
|
+
# which the repository does not hold). Names a redefine BODY
|
|
56
|
+
# redefines are NOT served from the target: the body is the
|
|
57
|
+
# effective type and is not resolvable here, so those keep
|
|
58
|
+
# the lenient unresolvable path (idF/idG/idH class).
|
|
59
|
+
@redefined_targets = []
|
|
60
|
+
@redefined_body_names = []
|
|
53
61
|
@elements = index_elements
|
|
54
62
|
end
|
|
55
63
|
|
|
@@ -178,6 +186,9 @@ module Lutaml
|
|
|
178
186
|
end
|
|
179
187
|
|
|
180
188
|
# Named type for an element's `type="prefix:Name"` reference.
|
|
189
|
+
# Same-local-name types across composed documents resolve by
|
|
190
|
+
# the OWNING declaration's namespace (schA1: ct-A exists in
|
|
191
|
+
# ns-a/ns-b/no-namespace; the reference binds its own).
|
|
181
192
|
#
|
|
182
193
|
# @param schema_element [Lutaml::Xml::Schema::Xsd::Element, nil]
|
|
183
194
|
# @return [Object, nil]
|
|
@@ -187,18 +198,43 @@ module Lutaml
|
|
|
187
198
|
reference = schema_element.type
|
|
188
199
|
return nil if reference.nil? || reference.to_s.empty?
|
|
189
200
|
|
|
190
|
-
resolve_named_type(reference.to_s.split(":").last
|
|
201
|
+
resolve_named_type(reference.to_s.split(":").last,
|
|
202
|
+
target_namespace_for(schema_element))
|
|
191
203
|
end
|
|
192
204
|
|
|
193
|
-
# Named complex or simple type by local name
|
|
205
|
+
# Named complex or simple type by local name, preferring a
|
|
206
|
+
# candidate declared in owner_ns.
|
|
194
207
|
#
|
|
195
208
|
# @param local_name [String]
|
|
209
|
+
# @param owner_ns [String, nil] the referencing declaration's
|
|
210
|
+
# target namespace
|
|
196
211
|
# @return [Object, nil]
|
|
197
|
-
def resolve_named_type(local_name)
|
|
212
|
+
def resolve_named_type(local_name, owner_ns = nil)
|
|
198
213
|
return nil if local_name.nil? || local_name.empty?
|
|
199
|
-
return @named_types[local_name] if @named_types.key?(local_name)
|
|
200
214
|
|
|
201
|
-
|
|
215
|
+
list = named_type_candidates(local_name)
|
|
216
|
+
return nil if list.empty?
|
|
217
|
+
# A nil owner means the referencing declaration lives in NO
|
|
218
|
+
# namespace (schA1_c): prefer the no-namespace candidate, not
|
|
219
|
+
# just the first.
|
|
220
|
+
if owner_ns.nil?
|
|
221
|
+
return list.find { |t| @namespaces[t].to_s.empty? } ||
|
|
222
|
+
list.first
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
list.find { |t| @namespaces[t] == owner_ns } || list.first
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Every top-level and nested declaration of the name, in
|
|
229
|
+
# document order: schemas first, then parsed redefine targets
|
|
230
|
+
# (names a redefine body redefines stay off the targets).
|
|
231
|
+
#
|
|
232
|
+
# @return [Array<Object>]
|
|
233
|
+
def named_type_candidates(local_name)
|
|
234
|
+
cache = @named_type_lists ||= {}
|
|
235
|
+
return cache[local_name] if cache.key?(local_name)
|
|
236
|
+
|
|
237
|
+
list = []
|
|
202
238
|
schemas.each do |schema|
|
|
203
239
|
found = Array(schema.complex_type).find do |ct|
|
|
204
240
|
ct.name == local_name
|
|
@@ -206,10 +242,23 @@ module Lutaml
|
|
|
206
242
|
found ||= Array(schema.simple_type).find do |st|
|
|
207
243
|
st.name == local_name
|
|
208
244
|
end
|
|
209
|
-
|
|
210
|
-
|
|
245
|
+
list << found if found
|
|
246
|
+
|
|
247
|
+
nested = find_nested_named_type(local_name, schema)
|
|
248
|
+
list << nested if nested
|
|
249
|
+
end
|
|
250
|
+
unless @redefined_body_names.include?(local_name)
|
|
251
|
+
@redefined_targets.each do |schema|
|
|
252
|
+
found = Array(schema.complex_type).find do |ct|
|
|
253
|
+
ct.name == local_name
|
|
254
|
+
end
|
|
255
|
+
found ||= Array(schema.simple_type).find do |st|
|
|
256
|
+
st.name == local_name
|
|
257
|
+
end
|
|
258
|
+
list << found if found
|
|
259
|
+
end
|
|
211
260
|
end
|
|
212
|
-
|
|
261
|
+
cache[local_name] = list.uniq
|
|
213
262
|
end
|
|
214
263
|
|
|
215
264
|
# Named types declared in documents reached through resolved
|
|
@@ -602,35 +651,60 @@ module Lutaml
|
|
|
602
651
|
# indexed under the name (attgC007/attgD036). Runs after the
|
|
603
652
|
# nested descent so included declarations are already in.
|
|
604
653
|
Array(schema.redefine).each do |red|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
654
|
+
index_redefine(index, schema, red, held_signatures, namespace,
|
|
655
|
+
defaults_schema)
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# xs:redefine replaces an attributeGroup with a body whose
|
|
660
|
+
# self-reference is the §4.2.2 hook to the ORIGINAL
|
|
661
|
+
# definition. lutaml-model does NOT chase the redefine
|
|
662
|
+
# target, so the original document is parsed here (relative
|
|
663
|
+
# to the defining file) and indexed; the original
|
|
664
|
+
# declaration is kept for that hook and the redefining body
|
|
665
|
+
# indexed under the name (attgC007/attgD036). Runs after the
|
|
666
|
+
# nested descent so included declarations are already in.
|
|
667
|
+
def index_redefine(index, schema, red, held_signatures, namespace,
|
|
668
|
+
defaults_schema)
|
|
669
|
+
target_path = nil
|
|
670
|
+
base = begin
|
|
671
|
+
File.dirname(@repository.source_path_for(schema))
|
|
672
|
+
rescue StandardError
|
|
673
|
+
nil
|
|
674
|
+
end
|
|
675
|
+
if base && red.schema_path.to_s.match?(/\S/)
|
|
676
|
+
candidate = File.expand_path(red.schema_path.to_s, base)
|
|
677
|
+
target_path = candidate if File.exist?(candidate)
|
|
678
|
+
end
|
|
679
|
+
if target_path
|
|
680
|
+
begin
|
|
681
|
+
target = Lutaml::Xml::Schema::Xsd.parse(
|
|
682
|
+
File.read(target_path), location: target_path
|
|
683
|
+
)
|
|
684
|
+
@redefined_targets << target
|
|
685
|
+
index_schema(index, target, held_signatures, namespace,
|
|
686
|
+
defaults_schema)
|
|
608
687
|
rescue StandardError
|
|
609
688
|
nil
|
|
610
689
|
end
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
end
|
|
626
|
-
Array(red.attribute_group).each do |group|
|
|
627
|
-
name = group.name.to_s
|
|
628
|
-
next if name.empty?
|
|
690
|
+
end
|
|
691
|
+
# Names the body redefines: served by the body (not
|
|
692
|
+
# resolvable here), never by the target originals.
|
|
693
|
+
Array(red.complex_type).each do |body|
|
|
694
|
+
name = body.name.to_s
|
|
695
|
+
@redefined_body_names << name unless name.empty?
|
|
696
|
+
end
|
|
697
|
+
Array(red.simple_type).each do |body|
|
|
698
|
+
name = body.name.to_s
|
|
699
|
+
@redefined_body_names << name unless name.empty?
|
|
700
|
+
end
|
|
701
|
+
Array(red.attribute_group).each do |group|
|
|
702
|
+
name = group.name.to_s
|
|
703
|
+
next if name.empty?
|
|
629
704
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
end
|
|
705
|
+
original = @attribute_groups[name]
|
|
706
|
+
@redefined_originals[name] = original if original
|
|
707
|
+
@attribute_groups[name] = group
|
|
634
708
|
end
|
|
635
709
|
end
|
|
636
710
|
|
|
@@ -723,6 +797,7 @@ module Lutaml
|
|
|
723
797
|
def index_type(index, complex_type, namespace, schema)
|
|
724
798
|
return unless complex_type
|
|
725
799
|
|
|
800
|
+
@namespaces[complex_type] = namespace
|
|
726
801
|
each_particle_element(complex_type, schema) do |element|
|
|
727
802
|
index_element(index, element, namespace, :inline, schema)
|
|
728
803
|
end
|
data/lib/lutaml/xsd/version.rb
CHANGED