lutaml-xsd 1.2.113 → 1.2.115
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 +80 -2
- data/lib/lutaml/xsd/validation/content_model_matcher.rb +6 -3
- data/lib/lutaml/xsd/validation/occurrence_product_guard.rb +6 -2
- data/lib/lutaml/xsd/validation/rules/content_model_validation_rule.rb +1 -0
- data/lib/lutaml/xsd/validation/rules/occurrence_validation_rule.rb +9 -1
- data/lib/lutaml/xsd/validation/rules/type_validation_rule.rb +319 -32
- data/lib/lutaml/xsd/validation/value_space.rb +138 -0
- 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: 8361d002b4925f8e9617b1befc010cdf69eda020d9819ad6cb563d5716fd0cb3
|
|
4
|
+
data.tar.gz: d7ee489b009dbfe0f537b6ec11ea9e68435161619db337801a8b2fe44881b267
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a67d033744fd14e969811aed1d8b7f7e679a50b2c535a1ea649e670d5e4baf6f5fef3c5b11349cfef519dbfb0cdacdc467270ccf6c3c231d3f6f234b2c7f84b2
|
|
7
|
+
data.tar.gz: 02fec25f4e6c24ecf8c591e8fff9dfb245acaeacb41a09c36223ae6a1da1cdc8e35a22c215b2a11c137982b1d49c284c568e926faf49d856bcc52f68a13be1a3
|
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 | 4664/4696 (99.32%) |
|
|
124
124
|
|
|
125
125
|
(2026-11-02, bundled-stack measurement with the escape,
|
|
126
126
|
hint-following, pattern-alternative, identity-crash, engine-set,
|
|
@@ -144,7 +144,7 @@ lockfile refresh) → 4411 (93.9%) → 4430 (94.3%) → 4435 (94.4%) →
|
|
|
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
146
|
round) → 4578 (97.49%, attribute-prohibition round) → 4585 (97.64%,
|
|
147
|
-
multi-document round) → 4588 (97.7%, type-binding round) → 4592 (97.79%, model-161 ride) → 4611 (98.19%, engine-406 ride) → 4613 (98.23%, temporal-enumeration round) → 4614 (98.25%, simple-type-children round) → 4617 (98.32%, ic-ancestor-scope round) → 4637 (parent-context rounds + chameleon round 1.2.109) → 4637 (nested-type-candidates round, 1.2.110) → 4637 (redefine-cycle round, 1.2.111) → 4644 measured (98.96% of measured, train-adoption round: moxml 0.6.6 + leptris 1.9.418.0) → 4649 (98.91%, quadratic-dispatch round — first FULL-denominator measure, the 5 reZ budget exclusions retired), +
|
|
147
|
+
multi-document round) → 4588 (97.7%, type-binding round) → 4592 (97.79%, model-161 ride) → 4611 (98.19%, engine-406 ride) → 4613 (98.23%, temporal-enumeration round) → 4614 (98.25%, simple-type-children round) → 4617 (98.32%, ic-ancestor-scope round) → 4637 (parent-context rounds + chameleon round 1.2.109) → 4637 (nested-type-candidates round, 1.2.110) → 4637 (redefine-cycle round, 1.2.111) → 4644 measured (98.96% of measured, train-adoption round: moxml 0.6.6 + leptris 1.9.418.0) → 4649 (98.91%, quadratic-dispatch round — first FULL-denominator measure, the 5 reZ budget exclusions retired) → 4658 (99.19%, remainder-classification round — first measure above 99) → 4664 (99.32%, ride-419 round), +703 net over the 1.2.43 baseline. The escapes round (PR #197 — \d→\p{Nd},
|
|
148
148
|
\w/\W/\s to their XSD value spaces) collapses Regex 120→69;
|
|
149
149
|
same-step pattern facets now behave as alternatives and
|
|
150
150
|
whitespace-only text reaches the facets intact (PR #200), Regex
|
|
@@ -638,6 +638,84 @@ tuple keys, node-set field violations and absent-field rows handled, cross-eleme
|
|
|
638
638
|
ledger (47 still-failing in both — the standing remainder:
|
|
639
639
|
contested 24, the upstream filings' legs, the parked redesign
|
|
640
640
|
tails).
|
|
641
|
+
- REMAINDER-CLASSIFICATION ROUND (2026-11-12, release pending):
|
|
642
|
+
the live ledger re-derived the queue (the memory's schA1/D10
|
|
643
|
+
gaps were STALE — all three pass since the chameleon/type-
|
|
644
|
+
binding rounds); the 47-case remainder splits into 13
|
|
645
|
+
filed-upstream, ~25 contested/disputed, and NINE ours. ALL
|
|
646
|
+
NINE FIXED, zero regressions — four mechanism families:
|
|
647
|
+
(a) DERIVATION COMPLETENESS (stZ054/055/056/073b/ba): §4.1.2
|
|
648
|
+
anySimpleType is the ur-simple root — EVERY simple type derives
|
|
649
|
+
from it, restriction-of-builtin chains and pure lists/unions
|
|
650
|
+
alike, and a simpleContent derivation over a builtin reaches it
|
|
651
|
+
transitively; a BUILTIN override owes the §3.3.4 check against
|
|
652
|
+
NAMED declared types via the BUILTIN_ANCESTORS table
|
|
653
|
+
(ValueSpace) — identity or declared-in-ancestry, xs:anyType
|
|
654
|
+
derives from nothing but itself (stZ056), restriction-of-union
|
|
655
|
+
does NOT admit members (stZ073b), direct unions do (the
|
|
656
|
+
elemT071 rule, builtin members included). (b) NFA LOOP-BRANCH
|
|
657
|
+
BYPASS (particlesA013 + the matcher probe): with_occurrence's
|
|
658
|
+
unbounded branch wired ε-edges from the FIRST body copy's exit
|
|
659
|
+
to final — with min ≥ 2 those bypass the mandatory unrolled
|
|
660
|
+
copies and accept after one consumption; the edges are
|
|
661
|
+
redundant at min ≤ 1 (cursor == exit_state) and removed.
|
|
662
|
+
(c) CHOICE-MIN EMPTY BRANCH (particlesIe004): a branch whose
|
|
663
|
+
minimum is zero satisfies the choice with the EMPTY sequence —
|
|
664
|
+
the choice's own minimum cannot fail on a zero count.
|
|
665
|
+
(d) BLOCK IN WHOLE OR IN PART + EXTENSION TEXT (particlesIg003,
|
|
666
|
+
addB114): §3.3.4 blocks the derivation CHAIN — any_type_chain_
|
|
667
|
+
kinds walks every hop to the ur-type (a bare complexType
|
|
668
|
+
restricts anyType per §3.4.3, so ext→base→anyType carries a
|
|
669
|
+
restriction hop that block="restriction" catches), and a
|
|
670
|
+
simpleContent EXTENSION's text validates against the
|
|
671
|
+
extension's base type (addB114: '62' violates base A's facets
|
|
672
|
+
through E-A; the branch previously had only a comment).
|
|
673
|
+
Also routed products AT the OccurrenceProductGuard cap to the
|
|
674
|
+
Ruby NFA (`< CAP` — the engine answers lax on wildcard
|
|
675
|
+
minimums there) and admitted the parsed Xml::Schema::Xsd::Any
|
|
676
|
+
class to PARTICLE_TYPES + the matcher wildcard dispatch.
|
|
677
|
+
SUITE 4658/4696 (99.19%) — first measure above 99; rspec 1663
|
|
678
|
+
green; rubocop clean. REMAINDER 38: 13 filed-upstream
|
|
679
|
+
(leptris-rs#72/#73, #45 legs, attgC035-038), ~25
|
|
680
|
+
contested/disputed (reJ 14, reS/reT/reU/reDH vintages,
|
|
681
|
+
attZ014a/b 1.1 drift, gMonth_pattern/double018, particlesZ001).
|
|
682
|
+
TOOLING NOTE: rubocop -A broke the tri-state
|
|
683
|
+
builtin_derivation_cover — the nil ("not a builtin") became
|
|
684
|
+
false, which would have flagged every schema-type override
|
|
685
|
+
undervalued; caught by re-reading the -A diff (the standing
|
|
686
|
+
predicate-nil lesson, now with the rename fix applied).
|
|
687
|
+
- RIDE-419 ROUND (2026-11-13, release pending): leptris
|
|
688
|
+
1.9.419.0 closed BOTH overnight filings — leptris-rs#72
|
|
689
|
+
(wildZ013b/c: the namespace-less-import wildcard admission
|
|
690
|
+
restored) and #73 (attZ014a/b: the multi-xs:ID 1.0 reading
|
|
691
|
+
restored). The ride also surfaced that OUR fixed-value check
|
|
692
|
+
was lexical-only where the suite demands VALUE-space equality
|
|
693
|
+
through a union's first-accepting member (§4.1.7): stE050
|
|
694
|
+
('true' == fixed '1' as boolean-true) and stE055 (float forms)
|
|
695
|
+
now pass via ValueSpace.union_member_value_key + the
|
|
696
|
+
fixed_value_space_equal? comparison in TypeValidationRule
|
|
697
|
+
(union members split per the established member_types.to_s.
|
|
698
|
+
split pattern — Array() on the attribute yields ONE
|
|
699
|
+
space-separated string, which silently never matches; three
|
|
700
|
+
call sites corrected, including a latent one from the previous
|
|
701
|
+
round). stE065/066/096 remain ENGINE-bound — filed
|
|
702
|
+
leptris-rs#74 (the engine's own fixed-value comparison is
|
|
703
|
+
lexical-only for unions); attgC035-038 remain FACADE-bound —
|
|
704
|
+
filed moxml#545 (the closed-#522 thread's uncovered legs: the
|
|
705
|
+
strict re-check false-reports attributeGroup-redefine §4.2.2
|
|
706
|
+
hook members). SUITE 4664/4696 (99.32%), zero regressions;
|
|
707
|
+
rspec 1663 green; rubocop clean. INCIDENT (recovered
|
|
708
|
+
in-round): threading repository: into validate_fixed_content
|
|
709
|
+
via a bulk gsub landed a call inside validate_builtin_type,
|
|
710
|
+
whose scope has no repository local — NameError on every
|
|
711
|
+
builtin-typed element, a 686-case regression wave that
|
|
712
|
+
briefly looked like a leptris-419 breakage; the pin-back
|
|
713
|
+
battery exonerated the train (the wave reproduced on 418)
|
|
714
|
+
and the signature fix recovered the battery. LESSON: when
|
|
715
|
+
threading a parameter through call sites, verify the RECEIVER
|
|
716
|
+
method's signature carries it — a mechanical gsub crosses
|
|
717
|
+
scope boundaries silently, and the crash-as-invalid
|
|
718
|
+
convention turns the NameError into wholesale false verdicts.
|
|
641
719
|
- TRAIN ADOPTION REGRESSION — CHARACTER REFERENCES (2026-11-11):
|
|
642
720
|
the 2026-11-10 adoption verdict ("conformance-IDENTICAL") was
|
|
643
721
|
WRONG for one class: moxml 0.6.4 stopped decoding numeric
|
|
@@ -195,7 +195,7 @@ module Lutaml
|
|
|
195
195
|
with_occurrence(particle) { compile_all(particle) }
|
|
196
196
|
when Lutaml::Xml::Schema::Xsd::Group
|
|
197
197
|
compile_group(particle)
|
|
198
|
-
when Lutaml::Xsd::Any
|
|
198
|
+
when Lutaml::Xsd::Any, Lutaml::Xml::Schema::Xsd::Any
|
|
199
199
|
with_occurrence(particle) { leaf_fragment(:wildcard, particle) }
|
|
200
200
|
else
|
|
201
201
|
with_occurrence(particle) { leaf_fragment(:element, particle) }
|
|
@@ -330,10 +330,13 @@ module Lutaml
|
|
|
330
330
|
@epsilons[entry] << final if min.zero?
|
|
331
331
|
if max == UNBOUNDED || min > MAX_UNROLL ||
|
|
332
332
|
(max != UNBOUNDED && max - min > MAX_UNROLL)
|
|
333
|
+
# Loop-or-finish from the LAST mandatory copy's exit only.
|
|
334
|
+
# The first copy's exit must NOT shortcut to final — with
|
|
335
|
+
# min >= 2 the ε-edge would bypass the mandatory copies and
|
|
336
|
+
# accept after one consumption (particlesA013: any min=2
|
|
337
|
+
# matched by a single child).
|
|
333
338
|
@epsilons[cursor] << last_entry
|
|
334
339
|
@epsilons[cursor] << final
|
|
335
|
-
@epsilons[exit_state] << last_entry
|
|
336
|
-
@epsilons[exit_state] << final
|
|
337
340
|
else
|
|
338
341
|
@epsilons[cursor] << final
|
|
339
342
|
optional = max - min - (min.zero? && max.is_a?(Integer) ? 1 : 0)
|
|
@@ -62,7 +62,11 @@ module Lutaml
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def particles_safe?(particles)
|
|
65
|
-
|
|
65
|
+
# Products AT the cap route to the Ruby NFA too — the engine
|
|
66
|
+
# compiles them into exactly the automata this guard exists
|
|
67
|
+
# to avoid (particlesA013: any min=2 max=1000000; the engine
|
|
68
|
+
# answers lax on the wildcard minimum there).
|
|
69
|
+
particles.all? { |particle| product(particle, 1, 0) < CAP }
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
# Walks a type's content model. Nested elements referencing
|
|
@@ -80,7 +84,7 @@ module Lutaml
|
|
|
80
84
|
expanded = @group_expander&.call(particle)
|
|
81
85
|
expanded && particles_safe?(expanded)
|
|
82
86
|
else
|
|
83
|
-
product(particle, 1, 0)
|
|
87
|
+
product(particle, 1, 0) < CAP
|
|
84
88
|
end
|
|
85
89
|
end
|
|
86
90
|
end
|
|
@@ -415,7 +415,15 @@ module Lutaml
|
|
|
415
415
|
repository)
|
|
416
416
|
occurrence_count = choice_occurrence_count(present_count, choice, repository)
|
|
417
417
|
|
|
418
|
-
|
|
418
|
+
# A branch whose occurrence minimum is zero satisfies the
|
|
419
|
+
# choice with the EMPTY sequence (XSD particle semantics),
|
|
420
|
+
# so the choice's own minimum cannot fail on a zero count
|
|
421
|
+
# (particlesIe004: choice(e1 1, e2 0) against an empty doc).
|
|
422
|
+
empty_branch = choice_alternatives(choice).any? do |alt|
|
|
423
|
+
parse_occurs(alt.min_occurs, 1).zero?
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
if occurrence_count < min_occurs && !empty_branch
|
|
419
427
|
report_error(
|
|
420
428
|
collector,
|
|
421
429
|
code: "choice_min_occurs_violation",
|
|
@@ -26,6 +26,14 @@ module Lutaml
|
|
|
26
26
|
include EngineBacked
|
|
27
27
|
include ValueSpace
|
|
28
28
|
|
|
29
|
+
# The ur-simple root: every simple type derives from it
|
|
30
|
+
# (XSD 1.0 §4.1.2).
|
|
31
|
+
ANY_SIMPLE_TYPE_LOCAL = "anySimpleType"
|
|
32
|
+
|
|
33
|
+
# The ur-type: root of the type hierarchy, derives from
|
|
34
|
+
# nothing but itself.
|
|
35
|
+
ANY_TYPE_LOCAL = "anyType"
|
|
36
|
+
|
|
29
37
|
# Rule category
|
|
30
38
|
#
|
|
31
39
|
# @return [Symbol] :type
|
|
@@ -62,14 +70,14 @@ module Lutaml
|
|
|
62
70
|
repository)
|
|
63
71
|
unless type_def
|
|
64
72
|
# xsi:type may name a built-in — resolve_named_type only
|
|
65
|
-
# sees schema documents (addB028).
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
# sees schema documents (addB028). A builtin override
|
|
74
|
+
# still owes the §3.3.4 derivation against the declared
|
|
75
|
+
# type: anyType derives from nothing else, so overriding
|
|
76
|
+
# an anySimpleType declaration with xs:anyType is
|
|
77
|
+
# invalid (stZ056).
|
|
78
|
+
return if builtin_override_reference(xml_element) &&
|
|
79
|
+
builtin_override_handled?(xml_element, schema_element,
|
|
80
|
+
collector, repository)
|
|
73
81
|
|
|
74
82
|
report_error(
|
|
75
83
|
collector,
|
|
@@ -119,7 +127,8 @@ module Lutaml
|
|
|
119
127
|
# A type-less declaration is xs:anyType; its fixed value
|
|
120
128
|
# still constrains the lexical content (isDefault070).
|
|
121
129
|
validate_fixed_content(xml_element, schema_element,
|
|
122
|
-
xml_element.text_content, collector
|
|
130
|
+
xml_element.text_content, collector,
|
|
131
|
+
repository: repository)
|
|
123
132
|
return
|
|
124
133
|
end
|
|
125
134
|
|
|
@@ -135,12 +144,16 @@ module Lutaml
|
|
|
135
144
|
# complex walk checks the type's model, this checks the
|
|
136
145
|
# declaration's own fixed content (isDefault077).
|
|
137
146
|
validate_fixed_content(xml_element, schema_element,
|
|
138
|
-
xml_element.text_content, collector
|
|
139
|
-
|
|
147
|
+
xml_element.text_content, collector,
|
|
148
|
+
repository: repository)
|
|
149
|
+
validate_complex_type_content(xml_element, type_def, collector,
|
|
150
|
+
repository: repository,
|
|
151
|
+
declared: schema_element)
|
|
140
152
|
else
|
|
141
153
|
# Handle built-in types
|
|
142
154
|
validate_builtin_type(xml_element, type_def, collector,
|
|
143
|
-
declared: schema_element
|
|
155
|
+
declared: schema_element,
|
|
156
|
+
repository: repository)
|
|
144
157
|
end
|
|
145
158
|
end
|
|
146
159
|
|
|
@@ -172,7 +185,12 @@ module Lutaml
|
|
|
172
185
|
# from one (elemT071); the override's block-kind comes from
|
|
173
186
|
# the member chain (elemT074). Complex types derive through
|
|
174
187
|
# both content kinds — a simpleContent extension of a simple
|
|
175
|
-
# base is a derivation too (elemT058).
|
|
188
|
+
# base is a derivation too (elemT058). anySimpleType is the
|
|
189
|
+
# ur-simple root (§4.1.2): EVERY simple type derives from it
|
|
190
|
+
# — restriction-of-builtin chains and pure lists/unions alike
|
|
191
|
+
# (stZ054) — and a simpleContent derivation over a builtin
|
|
192
|
+
# base reaches it transitively (stZ055's extension of
|
|
193
|
+
# xs:string).
|
|
176
194
|
#
|
|
177
195
|
# @return [Boolean]
|
|
178
196
|
def validly_derived?(type, base_local, repository, depth = 0)
|
|
@@ -190,14 +208,19 @@ module Lutaml
|
|
|
190
208
|
part = derivation_part(type)
|
|
191
209
|
return false unless part
|
|
192
210
|
|
|
193
|
-
|
|
211
|
+
raw_base = part.base.to_s
|
|
212
|
+
next_local = raw_base.split(":").last
|
|
194
213
|
return true if next_local == base_local
|
|
214
|
+
return true if base_local == ANY_SIMPLE_TYPE_LOCAL &&
|
|
215
|
+
builtin_base?(raw_base)
|
|
195
216
|
|
|
196
217
|
validly_derived?(
|
|
197
218
|
repository&.resolve_named_type(next_local),
|
|
198
219
|
base_local, repository, depth + 1
|
|
199
220
|
)
|
|
200
221
|
when Lutaml::Xml::Schema::Xsd::SimpleType
|
|
222
|
+
return true if base_local == ANY_SIMPLE_TYPE_LOCAL
|
|
223
|
+
|
|
201
224
|
restriction = type.restriction
|
|
202
225
|
return false unless restriction
|
|
203
226
|
|
|
@@ -231,6 +254,18 @@ module Lutaml
|
|
|
231
254
|
end
|
|
232
255
|
end
|
|
233
256
|
|
|
257
|
+
# Whether a derivation base attribute names a BUILTIN type —
|
|
258
|
+
# the xs:/xsd:-prefixed spellings only. An unresolvable
|
|
259
|
+
# UNPREFIXED base is a lenient-fallback miss, never a builtin
|
|
260
|
+
# (the string-fallback lesson).
|
|
261
|
+
#
|
|
262
|
+
# @param raw_base [String]
|
|
263
|
+
# @return [Boolean]
|
|
264
|
+
def builtin_base?(raw_base)
|
|
265
|
+
prefix = raw_base.to_s.split(":", 2).first
|
|
266
|
+
ValueSpace::BUILTIN_PREFIXES.include?(prefix)
|
|
267
|
+
end
|
|
268
|
+
|
|
234
269
|
# XSD 1.0 §3.3.4 / §3.4.6: block on the element declaration
|
|
235
270
|
# AND on the declared complexType forbids replacing the type
|
|
236
271
|
# with one derived by the blocked kind. A type-less
|
|
@@ -241,15 +276,20 @@ module Lutaml
|
|
|
241
276
|
# @return [Boolean]
|
|
242
277
|
def override_blocked?(schema_element, override_type, repository)
|
|
243
278
|
declared_local = schema_element.type.to_s.split(":").last
|
|
244
|
-
|
|
279
|
+
kinds =
|
|
245
280
|
if declared_local.to_s.empty?
|
|
246
|
-
|
|
281
|
+
any_type_chain_kinds(override_type, repository)
|
|
247
282
|
else
|
|
248
|
-
derivation_kind_from(override_type, declared_local)
|
|
249
|
-
|
|
250
|
-
|
|
283
|
+
direct = derivation_kind_from(override_type, declared_local)
|
|
284
|
+
direct ||= member_derivation_kind(override_type,
|
|
285
|
+
declared_local,
|
|
286
|
+
repository)
|
|
287
|
+
direct ||= derivation_chain_kinds(override_type,
|
|
288
|
+
declared_local,
|
|
289
|
+
repository).first
|
|
290
|
+
Array(direct)
|
|
251
291
|
end
|
|
252
|
-
return false
|
|
292
|
+
return false if kinds.empty?
|
|
253
293
|
|
|
254
294
|
tokens = effective_block_tokens(schema_element, repository)
|
|
255
295
|
declared = repository&.resolve_named_type(declared_local)
|
|
@@ -261,7 +301,68 @@ module Lutaml
|
|
|
261
301
|
tokens.intersect?(%w[extension restriction])
|
|
262
302
|
|
|
263
303
|
blocked = tokens.include?("#all") ? %w[extension restriction] : tokens
|
|
264
|
-
blocked.include?(kind)
|
|
304
|
+
kinds.any? { |kind| blocked.include?(kind) }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# §3.3.4 clause 2.2.2: the block applies to the derivation
|
|
308
|
+
# IN WHOLE OR IN PART — the kinds of EVERY hop from the
|
|
309
|
+
# override down to the declared type (ctI's indirect shapes).
|
|
310
|
+
#
|
|
311
|
+
# @return [Array<String>]
|
|
312
|
+
def derivation_chain_kinds(type, declared_local, repository,
|
|
313
|
+
depth = 0)
|
|
314
|
+
return [] if type.nil? || declared_local.to_s.empty? || depth > 10
|
|
315
|
+
|
|
316
|
+
case type
|
|
317
|
+
when Lutaml::Xml::Schema::Xsd::ComplexType
|
|
318
|
+
part = derivation_part(type)
|
|
319
|
+
return [] unless part
|
|
320
|
+
|
|
321
|
+
kind = part.class.name.include?("Extension") ? "extension" : "restriction"
|
|
322
|
+
next_local = part.base.to_s.split(":").last
|
|
323
|
+
return [kind] if next_local == declared_local
|
|
324
|
+
|
|
325
|
+
[kind, *derivation_chain_kinds(
|
|
326
|
+
repository&.resolve_named_type(next_local), declared_local,
|
|
327
|
+
repository, depth + 1
|
|
328
|
+
)]
|
|
329
|
+
when Lutaml::Xml::Schema::Xsd::SimpleType
|
|
330
|
+
restriction = type.restriction
|
|
331
|
+
return [] unless restriction
|
|
332
|
+
|
|
333
|
+
next_local = restriction.base.to_s.split(":").last
|
|
334
|
+
return ["restriction"] if next_local == declared_local
|
|
335
|
+
|
|
336
|
+
derivation_chain_kinds(
|
|
337
|
+
repository&.resolve_named_type(next_local), declared_local,
|
|
338
|
+
repository, depth + 1
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Kinds along the override's whole chain down to the ur-type
|
|
344
|
+
# — a type-less declaration's block governs every hop
|
|
345
|
+
# (particlesIg003: ext extends base, base restricts anyType).
|
|
346
|
+
#
|
|
347
|
+
# @return [Array<String>]
|
|
348
|
+
def any_type_chain_kinds(type, repository, depth = 0)
|
|
349
|
+
return [] if type.nil? || depth > 10
|
|
350
|
+
|
|
351
|
+
if type.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType)
|
|
352
|
+
return ["restriction"]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
part = derivation_part(type)
|
|
356
|
+
# A bare complexType (no derivation marker) restricts the
|
|
357
|
+
# ur-type per §3.4.3.
|
|
358
|
+
return ["restriction"] unless part
|
|
359
|
+
|
|
360
|
+
kind = part.class.name.include?("Extension") ? "extension" : "restriction"
|
|
361
|
+
next_local = part.base.to_s.split(":").last
|
|
362
|
+
[kind, *any_type_chain_kinds(
|
|
363
|
+
repository&.resolve_named_type(next_local), repository,
|
|
364
|
+
depth + 1
|
|
365
|
+
)]
|
|
265
366
|
end
|
|
266
367
|
|
|
267
368
|
# Derivation kind of an override against a type-less
|
|
@@ -418,6 +519,96 @@ module Lutaml
|
|
|
418
519
|
"#{prefix}:#{local}"
|
|
419
520
|
end
|
|
420
521
|
|
|
522
|
+
# XSD 1.0 §3.3.4 clause 2.2: a builtin override owes valid
|
|
523
|
+
# derivation from the declared type. anyType derives from
|
|
524
|
+
# nothing but itself; every other builtin derives from
|
|
525
|
+
# anySimpleType (§4.1.2 ancestry table); a named declared
|
|
526
|
+
# type must BE the builtin or one of its ancestors
|
|
527
|
+
# (stZ073b: xs:integer is no derivation of the
|
|
528
|
+
# restriction-of-union st.unionType); a declared UNION admits
|
|
529
|
+
# overrides naming a member or a builtin derived from one
|
|
530
|
+
# (the elemT071 rule, builtin members included).
|
|
531
|
+
#
|
|
532
|
+
# @return [Boolean]
|
|
533
|
+
def builtin_override_undervalued?(xml_element, schema_element,
|
|
534
|
+
repository)
|
|
535
|
+
override = xsi_type_name(xml_element).to_s
|
|
536
|
+
declared = declared_local_name(schema_element)
|
|
537
|
+
return false if declared.empty? || declared == override
|
|
538
|
+
return true if override == ANY_TYPE_LOCAL
|
|
539
|
+
return false if declared == ANY_SIMPLE_TYPE_LOCAL
|
|
540
|
+
|
|
541
|
+
covered = ValueSpace.builtin_derivation_cover(declared,
|
|
542
|
+
override)
|
|
543
|
+
return !covered unless covered.nil?
|
|
544
|
+
|
|
545
|
+
declared_type = repository&.resolve_named_type(declared)
|
|
546
|
+
return false unless declared_type.is_a?(
|
|
547
|
+
Lutaml::Xml::Schema::Xsd::SimpleType,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if declared_type.union
|
|
551
|
+
return !union_member_covers_builtin?(declared_type, override,
|
|
552
|
+
repository)
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
!validly_derived?(declared_type, override, repository)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Whether the override builtin is admitted by a declared
|
|
559
|
+
# union: it names a member, or a member's chain reaches it.
|
|
560
|
+
#
|
|
561
|
+
# @return [Boolean]
|
|
562
|
+
def union_member_covers_builtin?(union_type, override, repository)
|
|
563
|
+
members = union_type.union.member_types.to_s.split(/\s+/)
|
|
564
|
+
members.any? do |member|
|
|
565
|
+
local = member.to_s.split(":").last
|
|
566
|
+
next true if local == override
|
|
567
|
+
|
|
568
|
+
member_type = repository&.resolve_named_type(local)
|
|
569
|
+
next false unless member_type
|
|
570
|
+
|
|
571
|
+
validly_derived?(member_type, override, repository)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Handles an element whose xsi:type names a built-in: the
|
|
576
|
+
# §3.3.4 derivation gate, then builtin content validation.
|
|
577
|
+
#
|
|
578
|
+
# @return [Boolean] true when the element was handled
|
|
579
|
+
def builtin_override_handled?(xml_element, schema_element,
|
|
580
|
+
collector, repository)
|
|
581
|
+
if builtin_override_undervalued?(xml_element, schema_element,
|
|
582
|
+
repository)
|
|
583
|
+
report_error(
|
|
584
|
+
collector,
|
|
585
|
+
code: "xsi_type_not_derived",
|
|
586
|
+
message: "xsi:type '#{xsi_type_name(xml_element)}' is " \
|
|
587
|
+
"not validly derived from the declared type " \
|
|
588
|
+
"'#{declared_local_name(schema_element)}'",
|
|
589
|
+
location: xml_element.xpath,
|
|
590
|
+
context: { xsi_type: xsi_type_name(xml_element) },
|
|
591
|
+
)
|
|
592
|
+
return true
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
validate_builtin_type(xml_element,
|
|
596
|
+
builtin_override_reference(xml_element),
|
|
597
|
+
collector,
|
|
598
|
+
declared: schema_element,
|
|
599
|
+
repository: repository)
|
|
600
|
+
true
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# The declared type's local name ('' when the declaration is
|
|
604
|
+
# type-less — the implicit anyType).
|
|
605
|
+
#
|
|
606
|
+
# @param schema_element [Object]
|
|
607
|
+
# @return [String]
|
|
608
|
+
def declared_local_name(schema_element)
|
|
609
|
+
schema_element.type.to_s.split(":").last.to_s
|
|
610
|
+
end
|
|
611
|
+
|
|
421
612
|
# Resolve the type definition for an element
|
|
422
613
|
#
|
|
423
614
|
# @param schema_element [Lutaml::Xsd::Element, Lutaml::Xsd::ComplexType] Schema element
|
|
@@ -466,12 +657,17 @@ module Lutaml
|
|
|
466
657
|
end
|
|
467
658
|
|
|
468
659
|
# XSD 1.0 §3.3.3: a fixed value constrains the element — a
|
|
469
|
-
# non-empty lexical must equal it in the
|
|
660
|
+
# non-empty lexical must equal it in the VALUE space
|
|
470
661
|
# (elemQ018). Empty content already validates against the
|
|
471
|
-
# fixed value through effective_text.
|
|
662
|
+
# fixed value through effective_text. Union/list-typed
|
|
663
|
+
# declarations map each lexical through the FIRST accepting
|
|
664
|
+
# member (§4.1.7) — 'true' equals fixed '1' as boolean-true
|
|
665
|
+
# (stE050), token collapse equates trailing blanks
|
|
666
|
+
# (stE065/066), float forms compare numerically (stE055).
|
|
472
667
|
#
|
|
473
668
|
# @return [void]
|
|
474
|
-
def validate_fixed_content(xml_element, declared, value, collector
|
|
669
|
+
def validate_fixed_content(xml_element, declared, value, collector,
|
|
670
|
+
repository: nil)
|
|
475
671
|
return unless declared.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
476
672
|
|
|
477
673
|
fixed = declared.fixed.to_s
|
|
@@ -480,6 +676,8 @@ module Lutaml
|
|
|
480
676
|
|
|
481
677
|
rule = declared_whitespace_rule(declared)
|
|
482
678
|
return if canonical(value.to_s, rule) == canonical(fixed, rule)
|
|
679
|
+
return if fixed_value_space_equal?(declared, value.to_s, fixed,
|
|
680
|
+
repository)
|
|
483
681
|
|
|
484
682
|
report_error(
|
|
485
683
|
collector,
|
|
@@ -495,6 +693,76 @@ module Lutaml
|
|
|
495
693
|
)
|
|
496
694
|
end
|
|
497
695
|
|
|
696
|
+
# Value-space equality for union/list-typed declarations:
|
|
697
|
+
# each side keys through the first accepting member and the
|
|
698
|
+
# keys must match.
|
|
699
|
+
#
|
|
700
|
+
# @return [Boolean]
|
|
701
|
+
def fixed_value_space_equal?(declared, value, fixed, repository)
|
|
702
|
+
simple = declared.simple_type
|
|
703
|
+
return false unless simple
|
|
704
|
+
|
|
705
|
+
if simple.union
|
|
706
|
+
members = simple.union.member_types.to_s.split(/\s+/)
|
|
707
|
+
return union_keys_equal?(value, fixed, members, repository)
|
|
708
|
+
end
|
|
709
|
+
return false unless simple.list
|
|
710
|
+
|
|
711
|
+
item_union = inline_item_union(simple.list, repository)
|
|
712
|
+
return false unless item_union
|
|
713
|
+
|
|
714
|
+
left = value.split
|
|
715
|
+
right = fixed.split
|
|
716
|
+
return false unless left.size == right.size
|
|
717
|
+
|
|
718
|
+
left.zip(right).all? do |a, b|
|
|
719
|
+
union_keys_equal?(a, b, item_union, repository)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
# The member_types array of a list's item type: an inline
|
|
724
|
+
# union, else the named union resolved through the
|
|
725
|
+
# repository.
|
|
726
|
+
#
|
|
727
|
+
# @return [Array, nil]
|
|
728
|
+
def inline_item_union(list, repository)
|
|
729
|
+
inline = Array(list.simple_type).first
|
|
730
|
+
return inline.union.member_types.to_s.split(/\s+/) if inline&.union
|
|
731
|
+
|
|
732
|
+
reference = list.item_type.to_s.split(":").last
|
|
733
|
+
named = repository&.resolve_named_type(reference)
|
|
734
|
+
return named.union.member_types.to_s.split(/\s+/) if
|
|
735
|
+
named.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) && named.union
|
|
736
|
+
|
|
737
|
+
nil
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# @return [Boolean]
|
|
741
|
+
def union_keys_equal?(value, fixed, members, repository)
|
|
742
|
+
a = first_member_key(value, members, repository)
|
|
743
|
+
b = first_member_key(fixed, members, repository)
|
|
744
|
+
!a.nil? && !b.nil? && a == b
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
# @return [Object, nil]
|
|
748
|
+
def first_member_key(lexical, members, repository)
|
|
749
|
+
members.each do |member|
|
|
750
|
+
local = member.to_s.split(":").last
|
|
751
|
+
key = ValueSpace.union_member_value_key(local, lexical)
|
|
752
|
+
return key unless key.nil?
|
|
753
|
+
|
|
754
|
+
named = repository&.resolve_named_type(local)
|
|
755
|
+
next unless named.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType) &&
|
|
756
|
+
named.union
|
|
757
|
+
|
|
758
|
+
nested = first_member_key(lexical,
|
|
759
|
+
named.union.member_types.to_s.split(/\s+/),
|
|
760
|
+
repository)
|
|
761
|
+
return nested unless nested.nil?
|
|
762
|
+
end
|
|
763
|
+
nil
|
|
764
|
+
end
|
|
765
|
+
|
|
498
766
|
# @return [Symbol] :preserve, :replace or :collapse
|
|
499
767
|
def declared_whitespace_rule(declared)
|
|
500
768
|
reference = declared.type.to_s
|
|
@@ -543,7 +811,8 @@ module Lutaml
|
|
|
543
811
|
collector, repository: nil,
|
|
544
812
|
declared: nil)
|
|
545
813
|
value = effective_text(xml_element, declared)
|
|
546
|
-
validate_fixed_content(xml_element, declared, value, collector
|
|
814
|
+
validate_fixed_content(xml_element, declared, value, collector,
|
|
815
|
+
repository: repository)
|
|
547
816
|
|
|
548
817
|
# Validate base type if present
|
|
549
818
|
if simple_type.restriction
|
|
@@ -566,7 +835,8 @@ module Lutaml
|
|
|
566
835
|
# @param collector [ResultCollector] Result collector
|
|
567
836
|
# @return [void]
|
|
568
837
|
def validate_complex_type_content(xml_element, complex_type,
|
|
569
|
-
collector, repository: nil
|
|
838
|
+
collector, repository: nil,
|
|
839
|
+
declared: nil)
|
|
570
840
|
# Complex type validation is handled by:
|
|
571
841
|
# - AttributeValidationRule for attributes
|
|
572
842
|
# - ContentModelValidationRule for content model
|
|
@@ -575,7 +845,8 @@ module Lutaml
|
|
|
575
845
|
# Check for simple content
|
|
576
846
|
if complex_type.simple_content
|
|
577
847
|
validate_simple_content(xml_element, complex_type.simple_content,
|
|
578
|
-
collector, repository: repository
|
|
848
|
+
collector, repository: repository,
|
|
849
|
+
declared: declared)
|
|
579
850
|
end
|
|
580
851
|
end
|
|
581
852
|
|
|
@@ -1044,12 +1315,27 @@ module Lutaml
|
|
|
1044
1315
|
# @param collector [ResultCollector] Result collector
|
|
1045
1316
|
# @return [void]
|
|
1046
1317
|
def validate_simple_content(xml_element, simple_content, collector,
|
|
1047
|
-
repository: nil)
|
|
1318
|
+
repository: nil, declared: nil)
|
|
1048
1319
|
# Simple content means element has text content and attributes
|
|
1049
1320
|
# Validate the text content as a simple type
|
|
1050
1321
|
if simple_content.extension
|
|
1051
|
-
#
|
|
1052
|
-
#
|
|
1322
|
+
# The extension's TEXT still validates against the
|
|
1323
|
+
# extension's base type — the extension itself only adds
|
|
1324
|
+
# attributes (addB114: '62' violates base A's enumeration
|
|
1325
|
+
# and range through E-A's simpleContent extension).
|
|
1326
|
+
raw_base = simple_content.extension.base.to_s
|
|
1327
|
+
base_local = raw_base.split(":").last
|
|
1328
|
+
base_type = repository&.resolve_named_type(base_local)
|
|
1329
|
+
if base_type.is_a?(Lutaml::Xml::Schema::Xsd::SimpleType)
|
|
1330
|
+
validate_simple_type_content(xml_element, base_type,
|
|
1331
|
+
collector,
|
|
1332
|
+
repository: repository,
|
|
1333
|
+
declared: declared)
|
|
1334
|
+
elsif builtin_base?(raw_base)
|
|
1335
|
+
validate_builtin_type(xml_element, raw_base, collector,
|
|
1336
|
+
declared: declared,
|
|
1337
|
+
repository: repository)
|
|
1338
|
+
end
|
|
1053
1339
|
elsif simple_content.restriction
|
|
1054
1340
|
# Restriction constrains a simple type
|
|
1055
1341
|
value = xml_element.text_content
|
|
@@ -1070,9 +1356,10 @@ module Lutaml
|
|
|
1070
1356
|
# @param collector [ResultCollector] Result collector
|
|
1071
1357
|
# @return [void]
|
|
1072
1358
|
def validate_builtin_type(xml_element, type_name, collector,
|
|
1073
|
-
declared: nil)
|
|
1359
|
+
declared: nil, repository: nil)
|
|
1074
1360
|
value = effective_text(xml_element, declared)
|
|
1075
|
-
validate_fixed_content(xml_element, declared, value, collector
|
|
1361
|
+
validate_fixed_content(xml_element, declared, value, collector,
|
|
1362
|
+
repository: repository)
|
|
1076
1363
|
# A simple type has no element content: children under a
|
|
1077
1364
|
# builtin-typed element are illegal even when the lexical
|
|
1078
1365
|
# text is fine (dtZ86723 — the xsi:type-overridden root
|
|
@@ -66,6 +66,144 @@ module Lutaml
|
|
|
66
66
|
|
|
67
67
|
BUILTIN_PREFIXES = %w[xs xsd].freeze
|
|
68
68
|
|
|
69
|
+
# The built-in type hierarchy (XSD 1.0 §4.1.2, Figure 4-1 +
|
|
70
|
+
# §3.14.6): each local name maps to its ANCESTOR builtins.
|
|
71
|
+
# A builtin xsi:type override is validly derived from a
|
|
72
|
+
# declared type only when the declared name is the override
|
|
73
|
+
# itself or one of its ancestors (§3.3.4 clause 2.2).
|
|
74
|
+
BUILTIN_ANCESTORS = {
|
|
75
|
+
"anyType" => [].freeze,
|
|
76
|
+
"anySimpleType" => %w[anyType].freeze,
|
|
77
|
+
"string" => %w[anySimpleType anyType].freeze,
|
|
78
|
+
"normalizedString" => %w[string anySimpleType anyType].freeze,
|
|
79
|
+
"token" => %w[normalizedString string anySimpleType anyType].freeze,
|
|
80
|
+
"language" => %w[token normalizedString string anySimpleType anyType].freeze,
|
|
81
|
+
"Name" => %w[token normalizedString string anySimpleType anyType].freeze,
|
|
82
|
+
"NCName" => %w[Name token normalizedString string anySimpleType anyType].freeze,
|
|
83
|
+
"ID" => %w[NCName Name token normalizedString string anySimpleType anyType].freeze,
|
|
84
|
+
"IDREF" => %w[NCName Name token normalizedString string anySimpleType anyType].freeze,
|
|
85
|
+
"NMTOKEN" => %w[token normalizedString string anySimpleType anyType].freeze,
|
|
86
|
+
"IDREFS" => %w[IDREF NCName Name token normalizedString string anySimpleType anyType].freeze,
|
|
87
|
+
"ENTITY" => %w[NCName Name token normalizedString string anySimpleType anyType].freeze,
|
|
88
|
+
"ENTITIES" => %w[ENTITY NCName Name token normalizedString string anySimpleType anyType].freeze,
|
|
89
|
+
"NMTOKENS" => %w[NMTOKEN token normalizedString string anySimpleType anyType].freeze,
|
|
90
|
+
"decimal" => %w[anySimpleType anyType].freeze,
|
|
91
|
+
"integer" => %w[decimal anySimpleType anyType].freeze,
|
|
92
|
+
"long" => %w[integer decimal anySimpleType anyType].freeze,
|
|
93
|
+
"int" => %w[long integer decimal anySimpleType anyType].freeze,
|
|
94
|
+
"short" => %w[int long integer decimal anySimpleType anyType].freeze,
|
|
95
|
+
"byte" => %w[short int long integer decimal anySimpleType anyType].freeze,
|
|
96
|
+
"nonNegativeInteger" => %w[integer decimal anySimpleType anyType].freeze,
|
|
97
|
+
"positiveInteger" => %w[nonNegativeInteger integer decimal anySimpleType anyType].freeze,
|
|
98
|
+
"unsignedLong" => %w[nonNegativeInteger integer decimal anySimpleType anyType].freeze,
|
|
99
|
+
"unsignedInt" => %w[unsignedLong nonNegativeInteger integer decimal anySimpleType anyType].freeze,
|
|
100
|
+
"unsignedShort" => %w[unsignedInt unsignedLong nonNegativeInteger integer decimal anySimpleType anyType].freeze,
|
|
101
|
+
"unsignedByte" => %w[unsignedShort unsignedInt unsignedLong nonNegativeInteger integer decimal anySimpleType anyType].freeze,
|
|
102
|
+
"nonPositiveInteger" => %w[integer decimal anySimpleType anyType].freeze,
|
|
103
|
+
"negativeInteger" => %w[nonPositiveInteger integer decimal anySimpleType anyType].freeze,
|
|
104
|
+
"float" => %w[anySimpleType anyType].freeze,
|
|
105
|
+
"double" => %w[anySimpleType anyType].freeze,
|
|
106
|
+
"duration" => %w[anySimpleType anyType].freeze,
|
|
107
|
+
"dateTime" => %w[anySimpleType anyType].freeze,
|
|
108
|
+
"time" => %w[anySimpleType anyType].freeze,
|
|
109
|
+
"date" => %w[anySimpleType anyType].freeze,
|
|
110
|
+
"gYear" => %w[anySimpleType anyType].freeze,
|
|
111
|
+
"gYearMonth" => %w[anySimpleType anyType].freeze,
|
|
112
|
+
"gMonth" => %w[anySimpleType anyType].freeze,
|
|
113
|
+
"gMonthDay" => %w[anySimpleType anyType].freeze,
|
|
114
|
+
"gDay" => %w[anySimpleType anyType].freeze,
|
|
115
|
+
"boolean" => %w[anySimpleType anyType].freeze,
|
|
116
|
+
"base64Binary" => %w[anySimpleType anyType].freeze,
|
|
117
|
+
"hexBinary" => %w[anySimpleType anyType].freeze,
|
|
118
|
+
"anyURI" => %w[anySimpleType anyType].freeze,
|
|
119
|
+
"QName" => %w[anySimpleType anyType].freeze,
|
|
120
|
+
"NOTATION" => %w[QName anySimpleType anyType].freeze,
|
|
121
|
+
}.freeze
|
|
122
|
+
|
|
123
|
+
# The VALUE-SPACE key of a lexical under a union member
|
|
124
|
+
# builtin — nil when the member does not accept the lexical.
|
|
125
|
+
# A union maps a lexical through its FIRST accepting member
|
|
126
|
+
# (§4.1.7), so 'true' and '1' are both boolean-true (stE050)
|
|
127
|
+
# and token collapse equates trailing blanks (stE065/066).
|
|
128
|
+
#
|
|
129
|
+
# @param local [String] builtin member local name
|
|
130
|
+
# @param value [String] the lexical
|
|
131
|
+
# @return [Object, nil] a comparable value, or nil
|
|
132
|
+
def self.union_member_value_key(local, value)
|
|
133
|
+
text = value.to_s
|
|
134
|
+
case local
|
|
135
|
+
when "boolean"
|
|
136
|
+
b = text.strip
|
|
137
|
+
return nil unless b.match?(/\A(?:true|false|1|0)\z/)
|
|
138
|
+
|
|
139
|
+
%w[true 1].include?(b)
|
|
140
|
+
when *INTEGER_TYPES
|
|
141
|
+
return nil unless integer_valid?(local, text)
|
|
142
|
+
|
|
143
|
+
Integer(text.strip)
|
|
144
|
+
when "float", "double"
|
|
145
|
+
return nil unless text.strip.match?(
|
|
146
|
+
/\A(?:[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|INF|-INF|NaN)\z/,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
f = Float(text.strip)
|
|
150
|
+
f.nan? ? "nan" : f
|
|
151
|
+
when "decimal"
|
|
152
|
+
return nil unless text.strip.match?(
|
|
153
|
+
/\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)\z/,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
require "bigdecimal"
|
|
157
|
+
BigDecimal(text.strip)
|
|
158
|
+
when "string"
|
|
159
|
+
text
|
|
160
|
+
when "normalizedString"
|
|
161
|
+
text.gsub(/[\t\n\r]/, " ")
|
|
162
|
+
when *COLLAPSED_STRING_TYPES, "language", "Name", "NCName",
|
|
163
|
+
"ID", "IDREF", "NMTOKEN", "anyURI"
|
|
164
|
+
text.strip.split(/\s+/).join(" ")
|
|
165
|
+
when "gYear", "gYearMonth", "gMonth", "gMonthDay", "gDay"
|
|
166
|
+
return nil unless calendar_lexical_valid?(local, text)
|
|
167
|
+
|
|
168
|
+
calendar_key(text)
|
|
169
|
+
when "date", "dateTime", "time"
|
|
170
|
+
return nil unless lexical_answer(local, text) == true
|
|
171
|
+
|
|
172
|
+
temporal_key(text)
|
|
173
|
+
when "duration"
|
|
174
|
+
return nil unless duration_valid?(text)
|
|
175
|
+
|
|
176
|
+
duration_key(text)
|
|
177
|
+
when "hexBinary"
|
|
178
|
+
return nil unless text.match?(/\A(?:[0-9a-fA-F]{2})*\z/)
|
|
179
|
+
|
|
180
|
+
text.upcase
|
|
181
|
+
when "base64Binary"
|
|
182
|
+
return nil unless base64_valid?(text)
|
|
183
|
+
|
|
184
|
+
text
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Whether a builtin override local is validly derived from the
|
|
189
|
+
# declared local: identity or the declared name sits in the
|
|
190
|
+
# override's builtin ancestry. TRI-STATE: nil when the override
|
|
191
|
+
# is not a builtin local at all (the caller falls through to
|
|
192
|
+
# schema-type resolution). No `?` suffix — rubocop -A rewrites
|
|
193
|
+
# nil-returning predicate guards to false and breaks the
|
|
194
|
+
# contract.
|
|
195
|
+
#
|
|
196
|
+
# @param declared [String] declared type local name
|
|
197
|
+
# @param override [String] override builtin local name
|
|
198
|
+
# @return [Boolean, nil]
|
|
199
|
+
def self.builtin_derivation_cover(declared, override)
|
|
200
|
+
ancestors = BUILTIN_ANCESTORS[override.to_s]
|
|
201
|
+
return nil unless ancestors
|
|
202
|
+
|
|
203
|
+
declared.to_s == override.to_s ||
|
|
204
|
+
ancestors.include?(declared.to_s)
|
|
205
|
+
end
|
|
206
|
+
|
|
69
207
|
# xsd:length counts list ITEMS for list types, not characters.
|
|
70
208
|
LIST_BASES = %w[IDREFS ENTITIES NMTOKENS].freeze
|
|
71
209
|
|
data/lib/lutaml/xsd/version.rb
CHANGED