lutaml-xsd 1.2.0 → 1.2.1
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/Gemfile.lock +44 -44
- data/TODO.cleanup.md +12 -2
- data/TODO.conformance.md +29 -33
- data/lib/lutaml/xsd/json_schema_exporter.rb +35 -8
- data/lib/lutaml/xsd/rng_to_xsd_converter.rb +67 -46
- data/lib/lutaml/xsd/schema_repository/type_index.rb +2 -2
- data/lib/lutaml/xsd/spa/schema_serializer.rb +22 -25
- data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +27 -1
- data/lib/lutaml/xsd/validation/rules/content_model_validation_rule.rb +28 -23
- data/lib/lutaml/xsd/validation/rules/identity_constraints_rule.rb +28 -5
- data/lib/lutaml/xsd/validation/schema_definition_resolver.rb +57 -27
- data/lib/lutaml/xsd/validation/validation_rule.rb +5 -5
- data/lib/lutaml/xsd/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c78737c6dd26a5aa2ab915675b5fc5ce6316590d776d9ef6d5947988ef804adf
|
|
4
|
+
data.tar.gz: 56dd63144161aa1f7065067fcc74cf434bf9d40e1e1059c7e48e3cd99da00124
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89fe169405a033ca52e85b7017e65075167766a5bfba5b2c779483b7ff9ca0ea0a568daa799fdee61009e1e385af0c584b91e7e4af1bbcd578da67501e626107
|
|
7
|
+
data.tar.gz: b18d6e86f3ee582dbe52ace10581e2ea015681f6742bee4601b5a202e9ebc5e16e1878af8763cee247b2c452a18fe817d4e585cd067b403f5667ef52243c2ae9
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
lutaml-xsd (1.
|
|
4
|
+
lutaml-xsd (1.2.0)
|
|
5
5
|
liquid (>= 4.0, < 6.0)
|
|
6
6
|
lutaml-model (~> 0.8.114)
|
|
7
7
|
lutaml-store (~> 0.3)
|
|
@@ -20,7 +20,7 @@ GEM
|
|
|
20
20
|
ast (2.4.3)
|
|
21
21
|
base64 (0.3.0)
|
|
22
22
|
bigdecimal (4.1.3)
|
|
23
|
-
canon (0.3.
|
|
23
|
+
canon (0.3.110)
|
|
24
24
|
diff-lcs
|
|
25
25
|
json
|
|
26
26
|
leptris (~> 1.9.376)
|
|
@@ -46,29 +46,29 @@ GEM
|
|
|
46
46
|
fiddle (1.1.8)
|
|
47
47
|
json (3.1.0)
|
|
48
48
|
language_server-protocol (3.17.0.6)
|
|
49
|
-
leptris (1.9.
|
|
49
|
+
leptris (1.9.380.2-aarch64-linux)
|
|
50
50
|
ffi (~> 1.15)
|
|
51
|
-
leptris (1.9.
|
|
51
|
+
leptris (1.9.380.2-aarch64-linux-musl)
|
|
52
52
|
ffi (~> 1.15)
|
|
53
|
-
leptris (1.9.
|
|
53
|
+
leptris (1.9.380.2-arm-linux)
|
|
54
54
|
ffi (~> 1.15)
|
|
55
|
-
leptris (1.9.
|
|
55
|
+
leptris (1.9.380.2-arm-linux-musl)
|
|
56
56
|
ffi (~> 1.15)
|
|
57
|
-
leptris (1.9.
|
|
57
|
+
leptris (1.9.380.2-arm64-darwin)
|
|
58
58
|
ffi (~> 1.15)
|
|
59
|
-
leptris (1.9.
|
|
59
|
+
leptris (1.9.380.2-x64-mingw-ucrt)
|
|
60
60
|
ffi (~> 1.15)
|
|
61
|
-
leptris (1.9.
|
|
61
|
+
leptris (1.9.380.2-x86_64-darwin)
|
|
62
62
|
ffi (~> 1.15)
|
|
63
|
-
leptris (1.9.
|
|
63
|
+
leptris (1.9.380.2-x86_64-linux)
|
|
64
64
|
ffi (~> 1.15)
|
|
65
|
-
leptris (1.9.
|
|
65
|
+
leptris (1.9.380.2-x86_64-linux-musl)
|
|
66
66
|
ffi (~> 1.15)
|
|
67
67
|
lint_roller (1.1.0)
|
|
68
68
|
liquid (5.14.0)
|
|
69
69
|
bigdecimal
|
|
70
70
|
strscan (>= 3.1.1)
|
|
71
|
-
lutaml-model (0.8.
|
|
71
|
+
lutaml-model (0.8.123)
|
|
72
72
|
base64 (>= 0.1.1)
|
|
73
73
|
bigdecimal
|
|
74
74
|
canon
|
|
@@ -82,7 +82,7 @@ GEM
|
|
|
82
82
|
lutaml-model (~> 0.8.15)
|
|
83
83
|
rubyzip (>= 2.3, < 4)
|
|
84
84
|
memo_wise (1.13.0)
|
|
85
|
-
moxml (0.5.
|
|
85
|
+
moxml (0.5.153)
|
|
86
86
|
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
87
87
|
racc (~> 1.4)
|
|
88
88
|
nokogiri (1.19.4-aarch64-linux-musl)
|
|
@@ -182,23 +182,23 @@ GEM
|
|
|
182
182
|
xsdvi (1.0.2)
|
|
183
183
|
nokogiri (~> 1.16)
|
|
184
184
|
thor (~> 1.3)
|
|
185
|
-
yeptris (0.6.
|
|
185
|
+
yeptris (0.6.36.0-aarch64-linux)
|
|
186
186
|
ffi (~> 1.15)
|
|
187
|
-
yeptris (0.6.
|
|
187
|
+
yeptris (0.6.36.0-aarch64-linux-musl)
|
|
188
188
|
ffi (~> 1.15)
|
|
189
|
-
yeptris (0.6.
|
|
189
|
+
yeptris (0.6.36.0-arm-linux-eabihf)
|
|
190
190
|
ffi (~> 1.15)
|
|
191
|
-
yeptris (0.6.
|
|
191
|
+
yeptris (0.6.36.0-arm-linux-musleabihf)
|
|
192
192
|
ffi (~> 1.15)
|
|
193
|
-
yeptris (0.6.
|
|
193
|
+
yeptris (0.6.36.0-arm64-darwin)
|
|
194
194
|
ffi (~> 1.15)
|
|
195
|
-
yeptris (0.6.
|
|
195
|
+
yeptris (0.6.36.0-x64-mingw-ucrt)
|
|
196
196
|
ffi (~> 1.15)
|
|
197
|
-
yeptris (0.6.
|
|
197
|
+
yeptris (0.6.36.0-x86_64-darwin)
|
|
198
198
|
ffi (~> 1.15)
|
|
199
|
-
yeptris (0.6.
|
|
199
|
+
yeptris (0.6.36.0-x86_64-linux)
|
|
200
200
|
ffi (~> 1.15)
|
|
201
|
-
yeptris (0.6.
|
|
201
|
+
yeptris (0.6.36.0-x86_64-linux-musl)
|
|
202
202
|
ffi (~> 1.15)
|
|
203
203
|
|
|
204
204
|
PLATFORMS
|
|
@@ -235,7 +235,7 @@ CHECKSUMS
|
|
|
235
235
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
236
236
|
bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66
|
|
237
237
|
bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b
|
|
238
|
-
canon (0.3.
|
|
238
|
+
canon (0.3.110) sha256=4cec68aade805afb276f820adc5700feb0470bcbc75d97a70647ec9c338cedd4
|
|
239
239
|
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
|
|
240
240
|
csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
|
|
241
241
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
@@ -251,22 +251,22 @@ CHECKSUMS
|
|
|
251
251
|
fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
|
|
252
252
|
json (3.1.0) sha256=3ce7ccf8fde58960177a3a756c31f292a4c1e1dedee884547096a5b0604e3bd8
|
|
253
253
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
254
|
-
leptris (1.9.
|
|
255
|
-
leptris (1.9.
|
|
256
|
-
leptris (1.9.
|
|
257
|
-
leptris (1.9.
|
|
258
|
-
leptris (1.9.
|
|
259
|
-
leptris (1.9.
|
|
260
|
-
leptris (1.9.
|
|
261
|
-
leptris (1.9.
|
|
262
|
-
leptris (1.9.
|
|
254
|
+
leptris (1.9.380.2-aarch64-linux) sha256=63065f9b6a54b1a52eea5040439f946b4f1712729d277ed62eddea96526e2188
|
|
255
|
+
leptris (1.9.380.2-aarch64-linux-musl) sha256=6fed41560b33cec5edfacc205c2211c4110b0c48bf7c772baa3acd49efcbd2cc
|
|
256
|
+
leptris (1.9.380.2-arm-linux) sha256=553c135d0c50a5533b1726232b8c6a64ed3d903a006c786204a092e54c61ba01
|
|
257
|
+
leptris (1.9.380.2-arm-linux-musl) sha256=1172fad58c5f8f0b34d7701a3624ac4c3500ce963696137e821c5588737f1fb3
|
|
258
|
+
leptris (1.9.380.2-arm64-darwin) sha256=d94d55a2865744dec2d2b5ab3661de77400d3ca48fb36573c8dc58c3cd92c68f
|
|
259
|
+
leptris (1.9.380.2-x64-mingw-ucrt) sha256=7439cb09b2d0d9d88f3bb68ed832d0429a0e5019be4214d0342027c656538919
|
|
260
|
+
leptris (1.9.380.2-x86_64-darwin) sha256=e68a4c82bebcc5a5383eb76e0280be97c3b7edbf6f1eabf02e817ac057b49cfa
|
|
261
|
+
leptris (1.9.380.2-x86_64-linux) sha256=5b84a6ddc9bc297426bd36c15f4d0d878081954f3ab317a89cfb9fe36e8bd696
|
|
262
|
+
leptris (1.9.380.2-x86_64-linux-musl) sha256=abff7493153f8fd2516c056006866bb7fd680ab4fb1b83e89d92eca127a82cdb
|
|
263
263
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
264
264
|
liquid (5.14.0) sha256=e1020c2b2ee58c7a4d3bfdc7a331537bd27a783353748bff0bf27c6ac0c17065
|
|
265
|
-
lutaml-model (0.8.
|
|
265
|
+
lutaml-model (0.8.123) sha256=475f391371965f5baee2a9d018a7c9da8361c3774d3802749aa8805ee1253fde
|
|
266
266
|
lutaml-store (0.3.2) sha256=9c455dbab32b4c635c48a27213f6f56fda191731c4574eb6187184ba0588a672
|
|
267
|
-
lutaml-xsd (1.
|
|
267
|
+
lutaml-xsd (1.2.0)
|
|
268
268
|
memo_wise (1.13.0) sha256=30220c38c4cef410849bc73553c58664dc2c91c6379e4a1df22aea02358b716b
|
|
269
|
-
moxml (0.5.
|
|
269
|
+
moxml (0.5.153) sha256=561c6df843edd4a3b7f4c6785f9cfd6fef61050df7f1113fdcd45d8495704fc2
|
|
270
270
|
nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
|
|
271
271
|
nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af
|
|
272
272
|
nokogiri (1.19.4-arm-linux-gnu) sha256=a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca
|
|
@@ -313,15 +313,15 @@ CHECKSUMS
|
|
|
313
313
|
unicode-types (1.12.0) sha256=7e26a4352cecc2a50325a3793df834b732581ca9f25c5e29f46f3378934363f6
|
|
314
314
|
wisper (2.0.1) sha256=ce17bc5c3a166f241a2e6613848b025c8146fce2defba505920c1d1f3f88fae6
|
|
315
315
|
xsdvi (1.0.2) sha256=edf56a78f0fa25e4a5791d235ea8740cfb620eafa4abb79442b30160cf7292fc
|
|
316
|
-
yeptris (0.6.
|
|
317
|
-
yeptris (0.6.
|
|
318
|
-
yeptris (0.6.
|
|
319
|
-
yeptris (0.6.
|
|
320
|
-
yeptris (0.6.
|
|
321
|
-
yeptris (0.6.
|
|
322
|
-
yeptris (0.6.
|
|
323
|
-
yeptris (0.6.
|
|
324
|
-
yeptris (0.6.
|
|
316
|
+
yeptris (0.6.36.0-aarch64-linux) sha256=68d48f9cb4deab432175a3d336740dad58c14f2072bdf368d71122a435375828
|
|
317
|
+
yeptris (0.6.36.0-aarch64-linux-musl) sha256=5d074f3b3eb08cfd54d88bfbebfdc3c899fd8b882baf797759aded7794c3eac9
|
|
318
|
+
yeptris (0.6.36.0-arm-linux-eabihf) sha256=9f59f310251840527a75b4481b304c7d304a8eff346a8d7f3424277346174f9a
|
|
319
|
+
yeptris (0.6.36.0-arm-linux-musleabihf) sha256=966d0122b56c27a7889717fc1fe05c1bb2e1fa0eb10e9408a58c38f146514393
|
|
320
|
+
yeptris (0.6.36.0-arm64-darwin) sha256=70381795c9a8bb9d0264409a3d376fc67b61bb2057d0781517621297dccc8f40
|
|
321
|
+
yeptris (0.6.36.0-x64-mingw-ucrt) sha256=24a6cab2ac83ebb7d9f40a6281a7683fa837c9e260e99e413865fd49fa2068e1
|
|
322
|
+
yeptris (0.6.36.0-x86_64-darwin) sha256=dbc291383605b54b4755b43434cf0e69e5ec222d068a581ad9a7558aa7d7e64a
|
|
323
|
+
yeptris (0.6.36.0-x86_64-linux) sha256=5d88fa3cf4d69eec2411a6e724c39fbc3d2f4dc303e8462f260a0054248b6b6f
|
|
324
|
+
yeptris (0.6.36.0-x86_64-linux-musl) sha256=42cf7e2bcf74c5f141c42a253e94f76ffaf4d673ddd96c544ec12974d21953e4
|
|
325
325
|
|
|
326
326
|
BUNDLED WITH
|
|
327
327
|
4.0.23
|
data/TODO.cleanup.md
CHANGED
|
@@ -19,14 +19,24 @@ release-gated or need owner confirmation.
|
|
|
19
19
|
sequence: 7 choices / 1 group / 1 element — `sections` present via
|
|
20
20
|
bsi's override); gemspec pins `rng ~> 0.3.9` (released with the
|
|
21
21
|
include fixes + Moxml migration)
|
|
22
|
-
- [
|
|
23
|
-
owner confirmation
|
|
22
|
+
- [x] `lib/lutaml/xsd/schema_query_service.rb.bak` (untracked) — REMOVED
|
|
23
|
+
2026-10-22 with owner confirmation: byte-identical to commit
|
|
24
|
+
be55f18 on the apply-lutaml-store branch, which has since moved the
|
|
25
|
+
file forward; nothing unique was lost
|
|
24
26
|
- [ ] Prunable stale worktrees (`/private/tmp/fix/xsd`, `/private/tmp/xsd-main`)
|
|
25
27
|
— DEFERRED, owner to run `git worktree prune` after confirming the
|
|
26
28
|
branches hold nothing needed
|
|
27
29
|
- [ ] Dead-code sweep — DEFERRED, owner confirmation per file (nothing is
|
|
28
30
|
auto-deleted); the first candidate found in this run is
|
|
29
31
|
`RncToRngConverter#add_annotations` in rng (uncalled since 0.3.x)
|
|
32
|
+
- [x] respond_to? sweep — COMPLETE (2026-10-22): validation stack, SPA
|
|
33
|
+
serializer, JSON Schema exporter and type index (PR #121), then
|
|
34
|
+
`rng_to_xsd_converter.rb` (11 sites): class-keyed
|
|
35
|
+
PATTERN_KINDS_BY_CLASS from the rng 0.3.9 accessor matrices,
|
|
36
|
+
occurrence_assignable? for the min/max-occurs assignments
|
|
37
|
+
(Xsd::Attribute/AttributeGroup carry none), direct
|
|
38
|
+
documentation/name accessors. Gated by the converter corpus
|
|
39
|
+
(21 specs, 170 complex types)
|
|
30
40
|
- [ ] `Gemfile.lock` divergence guard for other lutaml gems — not needed
|
|
31
41
|
yet; adopt the same pattern when those repos start hitting the
|
|
32
42
|
fresh-resolve class of failure
|
data/TODO.conformance.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Goal: conformance is a number, not a feeling. Vendor the W3C XML Schema
|
|
4
4
|
test-suite categories, build the harness, publish the pass-rate, grow it.
|
|
5
5
|
|
|
6
|
-
## Baseline (2026-10-22, 20 cases / 42 checks)
|
|
6
|
+
## Baseline (2026-10-22, 20 cases / 42 checks, full pass)
|
|
7
7
|
|
|
8
8
|
Corpus: 20 case classes under `spec/conformance/cases/`, modelled on the
|
|
9
9
|
W3C XML Schema Test Suite category taxonomy — self-authored because the
|
|
@@ -12,34 +12,30 @@ can vendor a tarball later; provenance README goes with it).
|
|
|
12
12
|
|
|
13
13
|
| mode | pass-rate |
|
|
14
14
|
|---|---|
|
|
15
|
-
| engine (Moxml::XSD tier-1,
|
|
16
|
-
| pipeline (ValidationJob + wired rule engine) |
|
|
15
|
+
| engine (Moxml::XSD tier-1, leptris 1.9.380.2) | 38/42 (90.5%) |
|
|
16
|
+
| pipeline (ValidationJob + wired rule engine) | 42/42 (100.0%) |
|
|
17
17
|
|
|
18
|
-
The pipeline
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Gap register (each failure is a tracked gap, not a test bug):
|
|
26
|
-
|
|
27
|
-
- `attributes-any` valid-known — tier-1 engine reports a qualified
|
|
28
|
-
global attribute as "not declared" under strict anyAttribute (engine
|
|
29
|
-
limitation; pipeline accepts it correctly)
|
|
30
|
-
- `identity-keykeyref` — dangling keyref not detected: implemented in
|
|
31
|
-
`IdentityConstraintsRule`, but activation needs lutaml-model to map the
|
|
32
|
-
`refer` attribute on `xs:keyref` (upstream fix in flight)
|
|
33
|
-
- `xsi-type` — a VALID instance derived via `xsi:type` is rejected by the
|
|
34
|
-
tier-1 engine (false positive, structural: engine scope is stricter than
|
|
35
|
-
derivation); the unknown-type half passes in pipeline mode
|
|
36
|
-
- engine-only: `namespaces-qualified` wrong-namespace root accepted
|
|
37
|
-
(EngineContext root-matching skips it; pipeline's root check catches it)
|
|
18
|
+
The pipeline now passes every check in the corpus. The engine trails on
|
|
19
|
+
four: strict wildcard/anyAttribute under-detection, xsi:nil on
|
|
20
|
+
non-nillable elements, and the wrong-namespace root (root-matching
|
|
21
|
+
skips it) — all documented tier-1 laxness; the pipeline enforces them.
|
|
22
|
+
The engine's former xsi:type derivation FALSE POSITIVE is fixed in
|
|
23
|
+
leptris 1.9.380.2 (moxml#433 closed).
|
|
38
24
|
|
|
39
25
|
History: 2026-10-20 baseline 20/25 (80.0%) with pipeline == engine
|
|
40
26
|
(empty `RuleRegistry`, mismatched rule signatures); 2026-10-21 wiring
|
|
41
|
-
baseline 34/39 (87.2%); gap rules 37/39 (94.9%); anyAttribute
|
|
42
|
-
|
|
27
|
+
baseline 34/39 (87.2%); gap rules 37/39 (94.9%); anyAttribute 40/42
|
|
28
|
+
(95.2%); keyref activation via lutaml-model 0.8.121 → 41/42 (97.6%);
|
|
29
|
+
lutaml-model 0.8.123 (complexType/extension anyAttribute mappings) +
|
|
30
|
+
leptris 1.9.380.2 → 42/42 (100.0%).
|
|
31
|
+
|
|
32
|
+
Remaining engine-mode gaps (tracked, moxml/leptris-side):
|
|
33
|
+
|
|
34
|
+
- strict `xs:any` and `xs:anyAttribute` under-detection
|
|
35
|
+
(`invalid-undeclared` instances validate) — documented lax posture
|
|
36
|
+
- `xsi:nil` on non-nillable elements accepted
|
|
37
|
+
- `namespaces-qualified`: wrong-namespace root accepted (engine
|
|
38
|
+
root-matching skips it; the pipeline's root check catches it)
|
|
43
39
|
|
|
44
40
|
## Harness
|
|
45
41
|
|
|
@@ -80,19 +76,19 @@ pipeline to 40/42 (95.2%).
|
|
|
80
76
|
|
|
81
77
|
- [x] Identity constraints: `key`, `keyref`, `unique` —
|
|
82
78
|
`IdentityConstraintsRule` (selector/field XPath evaluation,
|
|
83
|
-
duplicate detection, keyref resolution); dangling-keyref
|
|
84
|
-
|
|
79
|
+
duplicate detection, keyref resolution); dangling-keyref detection
|
|
80
|
+
active since lutaml-model 0.8.121 (lutaml-model#990)
|
|
85
81
|
- [x] Wildcards: `any` namespace constraints (##any/##other/##local/
|
|
86
82
|
##targetNamespace/lists) + `processContents` strict declaration
|
|
87
83
|
checking in the content-model walk; `anyAttribute` honors namespace
|
|
88
|
-
constraints and strict checking
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
behavior until mapped
|
|
84
|
+
constraints and strict checking on every shape — attributeGroup and
|
|
85
|
+
restriction always, complexType-level and extension shapes since
|
|
86
|
+
lutaml-model 0.8.123 maps them (lutaml-model#995); the capability
|
|
87
|
+
probe keeps older models on pre-wildcard behavior
|
|
93
88
|
- [x] `xsi:nil` (nillable + empty-content checks) and `xsi:type`
|
|
94
89
|
resolution (unknown-type rejection; content walk follows the
|
|
95
|
-
override); valid-derived
|
|
90
|
+
override); the engine's valid-derived false positive is fixed in
|
|
91
|
+
leptris 1.9.380.2 (moxml#433)
|
|
96
92
|
- [ ] Particle checking: UPA detection — DEFERRED (engine covers the
|
|
97
93
|
tier-1 subset; UPA is schema-side checking)
|
|
98
94
|
- [ ] XSD 1.1 constructs behind a version flag — DEFERRED: 1.1 strategy
|
|
@@ -13,6 +13,21 @@ module Lutaml
|
|
|
13
13
|
class JsonSchemaExporter
|
|
14
14
|
DRAFT = "https://json-schema.org/draft/2020-12/schema"
|
|
15
15
|
|
|
16
|
+
# Nested particle accessors per container class — declared model
|
|
17
|
+
# attributes on lutaml-model 0.8.114+ (xs:all cannot nest, and
|
|
18
|
+
# only complexType/extension/restriction carry one level of
|
|
19
|
+
# sequence/choice/all).
|
|
20
|
+
PARTICLE_KINDS_BY_CLASS = {
|
|
21
|
+
Lutaml::Xml::Schema::Xsd::ComplexType => %i[sequence choice all],
|
|
22
|
+
Lutaml::Xml::Schema::Xsd::ExtensionComplexContent =>
|
|
23
|
+
%i[sequence choice all],
|
|
24
|
+
Lutaml::Xml::Schema::Xsd::RestrictionComplexContent =>
|
|
25
|
+
%i[sequence choice all],
|
|
26
|
+
Lutaml::Xml::Schema::Xsd::Sequence => %i[sequence choice],
|
|
27
|
+
Lutaml::Xml::Schema::Xsd::Choice => %i[sequence choice],
|
|
28
|
+
Lutaml::Xml::Schema::Xsd::All => [].freeze,
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
16
31
|
BUILTIN_TYPES = {
|
|
17
32
|
"string" => { "type" => "string" },
|
|
18
33
|
"normalizedString" => { "type" => "string" },
|
|
@@ -157,18 +172,30 @@ module Lutaml
|
|
|
157
172
|
def each_element_particle(node, &)
|
|
158
173
|
return unless node
|
|
159
174
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
kinds = PARTICLE_KINDS_BY_CLASS[node.class]
|
|
176
|
+
return unless kinds
|
|
177
|
+
|
|
178
|
+
kinds.each do |kind|
|
|
179
|
+
Array(node.public_send(kind)).each do |particle|
|
|
180
|
+
next unless particle
|
|
163
181
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
Array(particle.element).each(&)
|
|
183
|
+
Array(particle.choice).each do |choice|
|
|
184
|
+
each_element_particle(choice, &)
|
|
185
|
+
end
|
|
186
|
+
Array(particle.sequence).each do |seq|
|
|
187
|
+
each_element_particle(seq, &)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
167
190
|
end
|
|
168
|
-
|
|
191
|
+
|
|
192
|
+
return unless node.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType)
|
|
193
|
+
|
|
194
|
+
content = node.complex_content
|
|
195
|
+
return unless content
|
|
169
196
|
|
|
170
197
|
%i[extension restriction].each do |kind|
|
|
171
|
-
part =
|
|
198
|
+
part = content.public_send(kind)
|
|
172
199
|
each_element_particle(part, &) if part
|
|
173
200
|
end
|
|
174
201
|
end
|
|
@@ -51,8 +51,44 @@ module Lutaml
|
|
|
51
51
|
schema
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# Nested pattern accessors per container class — declared model
|
|
55
|
+
# attributes on rng 0.3.9 (Grammar carries only element; Start/List
|
|
56
|
+
# and Attribute omit one kind each; the other containers declare
|
|
57
|
+
# the full set).
|
|
58
|
+
FULL_PATTERN_KINDS = %w[element ref choice group interleave mixed
|
|
59
|
+
optional zeroOrMore oneOrMore text empty
|
|
60
|
+
value data list notAllowed attribute].freeze
|
|
61
|
+
PATTERN_KINDS_BY_CLASS = {
|
|
62
|
+
"Rng::Element" => FULL_PATTERN_KINDS,
|
|
63
|
+
"Rng::Choice" => FULL_PATTERN_KINDS,
|
|
64
|
+
"Rng::Group" => FULL_PATTERN_KINDS,
|
|
65
|
+
"Rng::Interleave" => FULL_PATTERN_KINDS,
|
|
66
|
+
"Rng::Mixed" => FULL_PATTERN_KINDS,
|
|
67
|
+
"Rng::Optional" => FULL_PATTERN_KINDS,
|
|
68
|
+
"Rng::ZeroOrMore" => FULL_PATTERN_KINDS,
|
|
69
|
+
"Rng::OneOrMore" => FULL_PATTERN_KINDS,
|
|
70
|
+
"Rng::Define" => FULL_PATTERN_KINDS,
|
|
71
|
+
"Rng::Attribute" =>
|
|
72
|
+
%w[ref choice group interleave mixed optional zeroOrMore
|
|
73
|
+
oneOrMore text empty value data list notAllowed attribute],
|
|
74
|
+
"Rng::Start" =>
|
|
75
|
+
%w[element ref choice group interleave mixed optional zeroOrMore
|
|
76
|
+
oneOrMore text empty value data list notAllowed],
|
|
77
|
+
"Rng::List" =>
|
|
78
|
+
%w[element ref choice group interleave mixed optional zeroOrMore
|
|
79
|
+
oneOrMore text empty value data notAllowed],
|
|
80
|
+
"Rng::Grammar" => %w[element],
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
54
83
|
private
|
|
55
84
|
|
|
85
|
+
# Occurrence attributes exist on particle models (element,
|
|
86
|
+
# containers); attribute declarations and attribute groups carry none.
|
|
87
|
+
def occurrence_assignable?(node)
|
|
88
|
+
!node.is_a?(Lutaml::Xml::Schema::Xsd::Attribute) &&
|
|
89
|
+
!node.is_a?(Lutaml::Xml::Schema::Xsd::AttributeGroup)
|
|
90
|
+
end
|
|
91
|
+
|
|
56
92
|
# Resolve a value that might be Lutaml::Model::UninitializedClass to nil or a string
|
|
57
93
|
def resolve_string(value)
|
|
58
94
|
return nil if value.nil? || value.is_a?(Lutaml::Model::UninitializedClass)
|
|
@@ -63,27 +99,20 @@ module Lutaml
|
|
|
63
99
|
# Collect all pattern children from an RNG container node
|
|
64
100
|
# Returns an array of non-nil pattern objects
|
|
65
101
|
def get_all_patterns(container)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
children = container.send(attr)
|
|
71
|
-
next if children.nil?
|
|
72
|
-
next if children.is_a?(Lutaml::Model::UninitializedClass)
|
|
73
|
-
|
|
74
|
-
if children.is_a?(Array)
|
|
75
|
-
patterns.concat(children.compact)
|
|
76
|
-
else
|
|
77
|
-
patterns << children
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
patterns
|
|
102
|
+
kinds = PATTERN_KINDS_BY_CLASS[container.class.name] || []
|
|
103
|
+
kinds.flat_map { |attr| Array(container.public_send(attr)) }
|
|
104
|
+
.compact
|
|
105
|
+
.reject { |child| child.is_a?(Lutaml::Model::UninitializedClass) }
|
|
81
106
|
end
|
|
82
107
|
|
|
83
|
-
#
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
# Get the single pattern child from a Start
|
|
109
|
+
def get_start_pattern(start)
|
|
110
|
+
kinds = PATTERN_KINDS_BY_CLASS[start.class.name] || []
|
|
111
|
+
kinds.map { |attr| start.public_send(attr) }
|
|
112
|
+
.find do |child|
|
|
113
|
+
child && !child.is_a?(Array) &&
|
|
114
|
+
!child.is_a?(Lutaml::Model::UninitializedClass)
|
|
115
|
+
end
|
|
87
116
|
end
|
|
88
117
|
|
|
89
118
|
# Build define name -> Define lookup from grammar (including divs)
|
|
@@ -925,17 +954,6 @@ module Lutaml
|
|
|
925
954
|
end
|
|
926
955
|
end
|
|
927
956
|
|
|
928
|
-
# Get the single pattern child from a Start
|
|
929
|
-
def get_start_pattern(start)
|
|
930
|
-
pattern_types.each do |attr|
|
|
931
|
-
next unless start.respond_to?(attr)
|
|
932
|
-
|
|
933
|
-
child = start.send(attr)
|
|
934
|
-
return child if child && !child.is_a?(Array) && !child.is_a?(Lutaml::Model::UninitializedClass)
|
|
935
|
-
end
|
|
936
|
-
nil
|
|
937
|
-
end
|
|
938
|
-
|
|
939
957
|
# Phase 2b: Convert top-level elements from grammar
|
|
940
958
|
def convert_grammar_elements(schema)
|
|
941
959
|
(@grammar.element || []).each do |rng_elem|
|
|
@@ -1034,7 +1052,7 @@ module Lutaml
|
|
|
1034
1052
|
xsd_elem = Lutaml::Xml::Schema::Xsd::Element.new(name: name)
|
|
1035
1053
|
|
|
1036
1054
|
# Add documentation if present
|
|
1037
|
-
if rng_elem.
|
|
1055
|
+
if rng_elem.documentation
|
|
1038
1056
|
xsd_elem.annotation = Lutaml::Xml::Schema::Xsd::Annotation.new(
|
|
1039
1057
|
documentation: [
|
|
1040
1058
|
Lutaml::Xml::Schema::Xsd::Documentation.new(
|
|
@@ -1134,7 +1152,7 @@ module Lutaml
|
|
|
1134
1152
|
xsd_attr.type = "xs:string"
|
|
1135
1153
|
end
|
|
1136
1154
|
|
|
1137
|
-
if rng_attr.
|
|
1155
|
+
if rng_attr.documentation
|
|
1138
1156
|
xsd_attr.annotation = Lutaml::Xml::Schema::Xsd::Annotation.new(
|
|
1139
1157
|
documentation: [
|
|
1140
1158
|
Lutaml::Xml::Schema::Xsd::Documentation.new(
|
|
@@ -1149,21 +1167,23 @@ module Lutaml
|
|
|
1149
1167
|
|
|
1150
1168
|
# Get the single pattern child from an Attribute
|
|
1151
1169
|
def get_attribute_child(attr)
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
nil
|
|
1170
|
+
kinds = PATTERN_KINDS_BY_CLASS[attr.class.name] || []
|
|
1171
|
+
kinds.map { |type_name| attr.public_send(type_name) }
|
|
1172
|
+
.find do |child|
|
|
1173
|
+
child && !child.is_a?(Array) &&
|
|
1174
|
+
!child.is_a?(Lutaml::Model::UninitializedClass)
|
|
1175
|
+
end
|
|
1159
1176
|
end
|
|
1160
1177
|
|
|
1161
|
-
# Extract element/attribute name from attr_name or name.value
|
|
1178
|
+
# Extract element/attribute name from attr_name or name.value;
|
|
1179
|
+
# only Rng::Element and Rng::Attribute carry these accessors.
|
|
1162
1180
|
def element_name(node)
|
|
1163
|
-
|
|
1164
|
-
|
|
1181
|
+
return nil unless node.is_a?(Rng::Element) || node.is_a?(Rng::Attribute)
|
|
1182
|
+
|
|
1183
|
+
name = node.attr_name
|
|
1184
|
+
if name.nil? || name.empty?
|
|
1165
1185
|
name_val = node.name
|
|
1166
|
-
name = name_val.value if name_val.
|
|
1186
|
+
name = name_val.value if name_val.is_a?(Rng::Name)
|
|
1167
1187
|
end
|
|
1168
1188
|
name
|
|
1169
1189
|
end
|
|
@@ -1226,7 +1246,8 @@ module Lutaml
|
|
|
1226
1246
|
end
|
|
1227
1247
|
|
|
1228
1248
|
# Per jing-trang: if any non-element child, wrap in optional
|
|
1229
|
-
|
|
1249
|
+
# (attribute groups carry no occurrence attributes)
|
|
1250
|
+
if has_non_element && occurrence_assignable?(result)
|
|
1230
1251
|
result.min_occurs = "0"
|
|
1231
1252
|
result.max_occurs = "1"
|
|
1232
1253
|
end
|
|
@@ -1279,7 +1300,7 @@ module Lutaml
|
|
|
1279
1300
|
|
|
1280
1301
|
if xsd_children.size == 1
|
|
1281
1302
|
child = xsd_children.first
|
|
1282
|
-
if
|
|
1303
|
+
if occurrence_assignable?(child)
|
|
1283
1304
|
child.min_occurs = min
|
|
1284
1305
|
child.max_occurs = max
|
|
1285
1306
|
end
|
|
@@ -1329,7 +1350,7 @@ module Lutaml
|
|
|
1329
1350
|
# Reference to a define promoted to top-level element -> element ref
|
|
1330
1351
|
# Use the element's actual name (not the define name) as the ref target,
|
|
1331
1352
|
# since they may differ (e.g., define "ext_toc" wraps element "name").
|
|
1332
|
-
elem_name = result[:element].
|
|
1353
|
+
elem_name = result[:element].name
|
|
1333
1354
|
Lutaml::Xml::Schema::Xsd::Element.new(ref: elem_name)
|
|
1334
1355
|
elsif result[:group]
|
|
1335
1356
|
Lutaml::Xml::Schema::Xsd::Group.new(ref: name)
|
|
@@ -61,11 +61,11 @@ module Lutaml
|
|
|
61
61
|
# @param namespace [String] The namespace URI
|
|
62
62
|
# @param file_path [String] Path to the schema file
|
|
63
63
|
def index_inline_complex_types(schema, namespace, file_path)
|
|
64
|
-
return unless schema.
|
|
64
|
+
return unless schema.element
|
|
65
65
|
|
|
66
66
|
elements = schema.element.is_a?(Array) ? schema.element : [schema.element]
|
|
67
67
|
elements.compact.each do |element|
|
|
68
|
-
next unless element.
|
|
68
|
+
next unless element.complex_type
|
|
69
69
|
|
|
70
70
|
inline_ct = element.complex_type
|
|
71
71
|
# Only index if it doesn't already have a name (truly anonymous)
|
|
@@ -575,9 +575,9 @@ module Lutaml
|
|
|
575
575
|
# @return [Array<Hash>] Serialized complex types (sorted alphabetically by name)
|
|
576
576
|
def serialize_complex_types(schema, prefix = nil, schema_source = nil,
|
|
577
577
|
file_path = nil)
|
|
578
|
-
return [] unless schema.
|
|
578
|
+
return [] unless schema.is_a?(Lutaml::Xml::Schema::Xsd::Schema)
|
|
579
579
|
|
|
580
|
-
types = schema.
|
|
580
|
+
types = schema.complex_types
|
|
581
581
|
serialized = types.map.with_index do |type, index|
|
|
582
582
|
serialize_complex_type(
|
|
583
583
|
type, index, prefix, schema_source, file_path
|
|
@@ -598,13 +598,13 @@ file_path = nil)
|
|
|
598
598
|
# @param file_path [String, nil] Optional file path for diagram generation
|
|
599
599
|
# @return [Array<Hash>] Serialized inline complex types
|
|
600
600
|
def serialize_inline_complex_types(schema, prefix = nil, _schema_source = nil, _file_path = nil)
|
|
601
|
-
return [] unless schema.
|
|
601
|
+
return [] unless schema.element
|
|
602
602
|
|
|
603
603
|
elements = schema.element.is_a?(Array) ? schema.element : [schema.element]
|
|
604
604
|
inline_types = []
|
|
605
605
|
|
|
606
606
|
elements.compact.each do |element|
|
|
607
|
-
next unless element.
|
|
607
|
+
next unless element.complex_type
|
|
608
608
|
next if element.complex_type.name # Skip if already named (top-level)
|
|
609
609
|
|
|
610
610
|
# Generate a synthetic name for the inline type
|
|
@@ -615,8 +615,8 @@ file_path = nil)
|
|
|
615
615
|
name: inline_name,
|
|
616
616
|
base: nil,
|
|
617
617
|
content_model: extract_content_model(element.complex_type),
|
|
618
|
-
abstract: element.complex_type.
|
|
619
|
-
mixed: element.complex_type.
|
|
618
|
+
abstract: element.complex_type.abstract || false,
|
|
619
|
+
mixed: element.complex_type.mixed || false,
|
|
620
620
|
attributes: serialize_type_attributes(element.complex_type),
|
|
621
621
|
elements: serialize_type_elements(element.complex_type),
|
|
622
622
|
choice: serialize_choice(element.complex_type.choice),
|
|
@@ -705,7 +705,7 @@ schema_source = nil, file_path = nil)
|
|
|
705
705
|
# @param model [Object] Model that may contain choice
|
|
706
706
|
# @return [Array<Hash>] Serialized choices
|
|
707
707
|
def serialize_choices(model)
|
|
708
|
-
return []
|
|
708
|
+
return [] unless model.choice
|
|
709
709
|
|
|
710
710
|
model_choices = if model.choice.is_a?(Array)
|
|
711
711
|
model.choice
|
|
@@ -743,7 +743,7 @@ schema_source = nil, file_path = nil)
|
|
|
743
743
|
# @param model [Object] Model that may contain sequence
|
|
744
744
|
# @return [Array<Hash>] Serialized sequences
|
|
745
745
|
def serialize_sequences(model)
|
|
746
|
-
return []
|
|
746
|
+
return [] unless model.sequence
|
|
747
747
|
|
|
748
748
|
model_sequences = if model.sequence.is_a?(Array)
|
|
749
749
|
model.sequence
|
|
@@ -880,9 +880,9 @@ schema_source = nil, file_path = nil)
|
|
|
880
880
|
# @param schema [Schema] Schema object
|
|
881
881
|
# @return [Array<Hash>] Serialized groups
|
|
882
882
|
def serialize_groups(schema, prefix = nil)
|
|
883
|
-
return [] unless schema.
|
|
883
|
+
return [] unless schema.groups
|
|
884
884
|
|
|
885
|
-
grps = schema.
|
|
885
|
+
grps = schema.groups
|
|
886
886
|
grps.map.with_index do |group, index|
|
|
887
887
|
# serialize_group(group, index, prefix)
|
|
888
888
|
serialize_type_group(group, index, prefix)
|
|
@@ -1081,8 +1081,8 @@ source = nil)
|
|
|
1081
1081
|
|
|
1082
1082
|
{
|
|
1083
1083
|
id: group_id(index, model, prefix),
|
|
1084
|
-
ref: model.
|
|
1085
|
-
name: model.
|
|
1084
|
+
ref: model.ref,
|
|
1085
|
+
name: model.name,
|
|
1086
1086
|
occurs: {
|
|
1087
1087
|
min: model.min_occurs || 1,
|
|
1088
1088
|
max: model.max_occurs || 1,
|
|
@@ -1098,7 +1098,7 @@ source = nil)
|
|
|
1098
1098
|
# @param model [Object] Model that may contain group
|
|
1099
1099
|
# @return [Array<Hash>] Serialized groups
|
|
1100
1100
|
def serialize_type_groups(model)
|
|
1101
|
-
return []
|
|
1101
|
+
return [] unless model.group
|
|
1102
1102
|
|
|
1103
1103
|
model_groups = if model.group.is_a?(Array)
|
|
1104
1104
|
model.group
|
|
@@ -1110,22 +1110,18 @@ source = nil)
|
|
|
1110
1110
|
end
|
|
1111
1111
|
end
|
|
1112
1112
|
|
|
1113
|
-
# Collect attribute group references from a
|
|
1113
|
+
# Collect attribute group references from a content wrapper
|
|
1114
|
+
# (simpleContent/complexContent — both declare extension and
|
|
1115
|
+
# restriction); the extensions carry the attribute groups.
|
|
1114
1116
|
#
|
|
1115
|
-
#
|
|
1116
|
-
#
|
|
1117
|
-
# @param model [Object] Any object that may have attribute_group or extension
|
|
1117
|
+
# @param model [SimpleContent, ComplexContent] Content wrapper
|
|
1118
1118
|
# @return [Array<Object>] Collected attribute group reference objects
|
|
1119
1119
|
def collect_attribute_group_refs(model)
|
|
1120
1120
|
refs = []
|
|
1121
1121
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
refs.concat(
|
|
1125
|
-
end
|
|
1126
|
-
|
|
1127
|
-
if model.respond_to?(:extension) && model.extension
|
|
1128
|
-
refs.concat(collect_extension_attribute_group_refs(model.extension))
|
|
1122
|
+
%i[extension restriction].each do |kind|
|
|
1123
|
+
part = model.public_send(kind)
|
|
1124
|
+
refs.concat(collect_extension_attribute_group_refs(part)) if part
|
|
1129
1125
|
end
|
|
1130
1126
|
|
|
1131
1127
|
refs
|
|
@@ -1385,7 +1381,8 @@ source = nil)
|
|
|
1385
1381
|
# @param type [ComplexType] Complex type
|
|
1386
1382
|
# @return [Array<Hash>] Serialized elements
|
|
1387
1383
|
def serialize_type_elements(type)
|
|
1388
|
-
return [] unless type.
|
|
1384
|
+
return [] unless type.is_a?(Lutaml::Xml::Schema::Xsd::Sequence) ||
|
|
1385
|
+
type.is_a?(Lutaml::Xml::Schema::Xsd::Choice)
|
|
1389
1386
|
|
|
1390
1387
|
type.element.map do |elem|
|
|
1391
1388
|
{
|
|
@@ -22,6 +22,14 @@ module Lutaml
|
|
|
22
22
|
#
|
|
23
23
|
# @see docs/JING_ALGORITHM_PORTING_GUIDE.md Attribute Validation
|
|
24
24
|
class AttributeValidationRule < ValidationRule
|
|
25
|
+
PROBE_SCHEMA = <<~XSD
|
|
26
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
27
|
+
<xs:complexType name="probe">
|
|
28
|
+
<xs:anyAttribute namespace="##local"/>
|
|
29
|
+
</xs:complexType>
|
|
30
|
+
</xs:schema>
|
|
31
|
+
XSD
|
|
32
|
+
|
|
25
33
|
CONTENT_KINDS = %i[complex_content simple_content].freeze
|
|
26
34
|
CONTENT_PART_KINDS = %i[extension restriction].freeze
|
|
27
35
|
|
|
@@ -371,6 +379,18 @@ module Lutaml
|
|
|
371
379
|
# Lutaml::Xsd::ComplexType, nil]
|
|
372
380
|
# @param repository [SchemaDefinitionResolver, nil]
|
|
373
381
|
# @return [AnyAttribute, nil]
|
|
382
|
+
# Whether the loaded lutaml-model parses xs:anyAttribute on
|
|
383
|
+
# complexType (the mapping landed after 0.8.121); probed once
|
|
384
|
+
# with a minimal schema.
|
|
385
|
+
def complex_type_wildcard_support?
|
|
386
|
+
@complex_type_wildcard_support ||= begin
|
|
387
|
+
probe = Lutaml::Xml::Schema::Xsd.parse(PROBE_SCHEMA)
|
|
388
|
+
!probe.complex_type.first.any_attribute.nil?
|
|
389
|
+
rescue StandardError
|
|
390
|
+
false
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
374
394
|
def collect_any_attribute(schema_def, repository = nil)
|
|
375
395
|
if schema_def.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
376
396
|
type_def = resolve_type(schema_def, repository)
|
|
@@ -378,6 +398,12 @@ module Lutaml
|
|
|
378
398
|
end
|
|
379
399
|
return nil unless schema_def
|
|
380
400
|
|
|
401
|
+
if schema_def.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType) &&
|
|
402
|
+
complex_type_wildcard_support?
|
|
403
|
+
wildcard = schema_def.any_attribute
|
|
404
|
+
return wildcard if wildcard
|
|
405
|
+
end
|
|
406
|
+
|
|
381
407
|
Array(schema_def.attribute_group).each do |group|
|
|
382
408
|
wildcard = Array(group.any_attribute).first
|
|
383
409
|
return wildcard if wildcard
|
|
@@ -389,7 +415,7 @@ module Lutaml
|
|
|
389
415
|
|
|
390
416
|
CONTENT_PART_KINDS.each do |part_kind|
|
|
391
417
|
part = Array(content.public_send(part_kind)).first
|
|
392
|
-
next unless part
|
|
418
|
+
next unless part
|
|
393
419
|
|
|
394
420
|
wildcard = Array(part.any_attribute).first
|
|
395
421
|
return wildcard if wildcard
|
|
@@ -22,6 +22,15 @@ module Lutaml
|
|
|
22
22
|
#
|
|
23
23
|
# @see docs/JING_ALGORITHM_PORTING_GUIDE.md Content Model Validation
|
|
24
24
|
class ContentModelValidationRule < ValidationRule
|
|
25
|
+
# Types whose particle accessors type_particles reads — all are
|
|
26
|
+
# declared model attributes (lutaml-model 0.8.114+).
|
|
27
|
+
COMPLEX_PARTICLE_TYPES = [
|
|
28
|
+
Lutaml::Xml::Schema::Xsd::ComplexType,
|
|
29
|
+
Lutaml::Xml::Schema::Xsd::ExtensionComplexContent,
|
|
30
|
+
Lutaml::Xml::Schema::Xsd::RestrictionComplexContent,
|
|
31
|
+
].freeze
|
|
32
|
+
COMPLEX_PARTICLE_KINDS = %i[sequence choice all group].freeze
|
|
33
|
+
|
|
25
34
|
# Rule category
|
|
26
35
|
#
|
|
27
36
|
# @return [Symbol] :structure
|
|
@@ -97,22 +106,21 @@ module Lutaml
|
|
|
97
106
|
end
|
|
98
107
|
end
|
|
99
108
|
|
|
100
|
-
# Ordered particles of a type: own sequence/choice/all/
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
109
|
+
# Ordered particles of a type: own sequence/choice/all/group
|
|
110
|
+
# particles plus, for extensions, the base type's particles
|
|
111
|
+
# first. Cycle-guarded. Particles are kept whole — nested
|
|
112
|
+
# containers are expanded by the walk's matching, which
|
|
104
113
|
# preserves their own occurrence constraints.
|
|
105
114
|
#
|
|
106
115
|
# @return [Array]
|
|
107
116
|
def type_particles(type, repository, seen)
|
|
108
117
|
return [] if type.nil? || seen.key?(type)
|
|
118
|
+
return [] unless COMPLEX_PARTICLE_TYPES.any? { |k| type.is_a?(k) }
|
|
109
119
|
|
|
110
120
|
seen[type] = true
|
|
111
121
|
particles = []
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
next unless type.respond_to?(kind)
|
|
115
|
-
|
|
123
|
+
COMPLEX_PARTICLE_KINDS.each do |kind|
|
|
116
124
|
Array(type.public_send(kind)).each do |particle|
|
|
117
125
|
next unless particle
|
|
118
126
|
|
|
@@ -127,7 +135,8 @@ module Lutaml
|
|
|
127
135
|
end
|
|
128
136
|
end
|
|
129
137
|
|
|
130
|
-
if type.
|
|
138
|
+
if type.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType) &&
|
|
139
|
+
type.complex_content
|
|
131
140
|
content = type.complex_content
|
|
132
141
|
extension = Array(content.extension).first
|
|
133
142
|
if extension
|
|
@@ -135,17 +144,15 @@ module Lutaml
|
|
|
135
144
|
base_type = base_local && repository&.resolve_named_type(base_local)
|
|
136
145
|
particles = type_particles(base_type, repository, seen) +
|
|
137
146
|
particles
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
else
|
|
148
|
-
particles << particle
|
|
147
|
+
COMPLEX_PARTICLE_KINDS.each do |kind|
|
|
148
|
+
Array(extension.public_send(kind)).each do |particle|
|
|
149
|
+
next unless particle
|
|
150
|
+
|
|
151
|
+
if particle.is_a?(Lutaml::Xsd::Sequence)
|
|
152
|
+
particles.concat(collect_particles(particle))
|
|
153
|
+
else
|
|
154
|
+
particles << particle
|
|
155
|
+
end
|
|
149
156
|
end
|
|
150
157
|
end
|
|
151
158
|
end
|
|
@@ -390,10 +397,8 @@ module Lutaml
|
|
|
390
397
|
def collect_particles(content_model)
|
|
391
398
|
# resolved_element_order preserves declaration order including
|
|
392
399
|
# xs:any particles (which child_elements drops).
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return content_model.resolved_element_order.to_a
|
|
396
|
-
end
|
|
400
|
+
ordered = content_model.resolved_element_order
|
|
401
|
+
return ordered.to_a if ordered.to_a.any?
|
|
397
402
|
|
|
398
403
|
particles = []
|
|
399
404
|
|
|
@@ -100,13 +100,33 @@ module Lutaml
|
|
|
100
100
|
def selector_xpaths(constraint)
|
|
101
101
|
return [] unless constraint.selector
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
selector = constraint.selector
|
|
104
|
+
if selector.is_a?(Lutaml::Xml::Schema::Xsd::Selector)
|
|
105
|
+
xpath_strings(selector.xpath)
|
|
106
|
+
else
|
|
107
|
+
xpath_strings(selector)
|
|
108
|
+
end
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
def field_xpaths(constraint)
|
|
108
|
-
Array(constraint.field).
|
|
109
|
-
field.
|
|
112
|
+
Array(constraint.field).flat_map do |field|
|
|
113
|
+
if field.is_a?(Lutaml::Xml::Schema::Xsd::Field)
|
|
114
|
+
xpath_strings(field.xpath)
|
|
115
|
+
else
|
|
116
|
+
xpath_strings(field)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Selector/field values are either the raw XPath string or the
|
|
122
|
+
# parsed xs:selector/xs:field model whose xpath attribute holds
|
|
123
|
+
# it; whitespace-separated lists yield one entry each.
|
|
124
|
+
def xpath_strings(value)
|
|
125
|
+
case value
|
|
126
|
+
when String
|
|
127
|
+
value.split(/\s+/).reject(&:empty?)
|
|
128
|
+
else
|
|
129
|
+
[]
|
|
110
130
|
end
|
|
111
131
|
end
|
|
112
132
|
|
|
@@ -114,7 +134,10 @@ module Lutaml
|
|
|
114
134
|
first = Array(node.xpath(field_xpath)).first
|
|
115
135
|
return nil unless first
|
|
116
136
|
|
|
117
|
-
|
|
137
|
+
case first
|
|
138
|
+
when Moxml::Attribute then first.value
|
|
139
|
+
else first.text
|
|
140
|
+
end
|
|
118
141
|
end
|
|
119
142
|
|
|
120
143
|
# Keyref `refer` as a string; nil when the model left it
|
|
@@ -15,7 +15,25 @@ module Lutaml
|
|
|
15
15
|
# Deliberately not resolved here (rule-engine backlog): dynamic
|
|
16
16
|
# typing via xsi:type and derivation closure.
|
|
17
17
|
class SchemaDefinitionResolver
|
|
18
|
-
|
|
18
|
+
# Nested particle accessors per container class — all are declared
|
|
19
|
+
# model attributes (lutaml-model 0.8.114+), so dispatch is by
|
|
20
|
+
# class rather than duck-typed probing. Sequence/Choice cannot
|
|
21
|
+
# nest xs:all, and only xs:all carries bare elements.
|
|
22
|
+
CONTAINER_NESTED_KINDS = {
|
|
23
|
+
Lutaml::Xml::Schema::Xsd::ComplexType => %i[sequence choice all],
|
|
24
|
+
Lutaml::Xml::Schema::Xsd::ExtensionComplexContent =>
|
|
25
|
+
%i[sequence choice all],
|
|
26
|
+
Lutaml::Xml::Schema::Xsd::RestrictionComplexContent =>
|
|
27
|
+
%i[sequence choice all],
|
|
28
|
+
Lutaml::Xml::Schema::Xsd::Sequence => %i[sequence choice],
|
|
29
|
+
Lutaml::Xml::Schema::Xsd::Choice => %i[sequence choice],
|
|
30
|
+
Lutaml::Xml::Schema::Xsd::All => [].freeze,
|
|
31
|
+
}.freeze
|
|
32
|
+
CONTAINER_LEAF_KINDS = {
|
|
33
|
+
Lutaml::Xml::Schema::Xsd::Sequence => %i[element any group],
|
|
34
|
+
Lutaml::Xml::Schema::Xsd::Choice => %i[element any group],
|
|
35
|
+
Lutaml::Xml::Schema::Xsd::All => %i[element].freeze,
|
|
36
|
+
}.freeze
|
|
19
37
|
|
|
20
38
|
def initialize(repository)
|
|
21
39
|
@repository = repository
|
|
@@ -126,21 +144,26 @@ module Lutaml
|
|
|
126
144
|
private
|
|
127
145
|
|
|
128
146
|
def particle_for_type(type)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
case type
|
|
148
|
+
when Lutaml::Xml::Schema::Xsd::ComplexType
|
|
149
|
+
direct = Array(type.sequence).first ||
|
|
150
|
+
Array(type.choice).first || Array(type.all).first
|
|
151
|
+
return direct if direct
|
|
152
|
+
|
|
153
|
+
# Derived types keep their particles under complex_content.
|
|
154
|
+
if type.complex_content
|
|
155
|
+
content = type.complex_content
|
|
156
|
+
part = Array(content.extension).first ||
|
|
157
|
+
Array(content.restriction).first
|
|
158
|
+
return particle_for_type(part) if part
|
|
159
|
+
end
|
|
142
160
|
|
|
143
|
-
|
|
161
|
+
nil
|
|
162
|
+
when Lutaml::Xml::Schema::Xsd::ExtensionComplexContent,
|
|
163
|
+
Lutaml::Xml::Schema::Xsd::RestrictionComplexContent
|
|
164
|
+
Array(type.sequence).first ||
|
|
165
|
+
Array(type.choice).first || Array(type.all).first
|
|
166
|
+
end
|
|
144
167
|
end
|
|
145
168
|
|
|
146
169
|
def schemas
|
|
@@ -216,25 +239,32 @@ module Lutaml
|
|
|
216
239
|
end
|
|
217
240
|
return if visited_particle?(node)
|
|
218
241
|
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
nested = CONTAINER_NESTED_KINDS[node.class]
|
|
243
|
+
return unless nested
|
|
221
244
|
|
|
245
|
+
nested.each do |kind|
|
|
222
246
|
Array(node.public_send(kind)).each do |particle|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
each_particle_element(particle, &) if particle
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
(CONTAINER_LEAF_KINDS[node.class] || []).each do |kind|
|
|
251
|
+
Array(node.public_send(kind)).each do |leaf|
|
|
252
|
+
# xs:any wildcards and xs:group references are not element
|
|
253
|
+
# declarations; the index holds Element models only.
|
|
254
|
+
yield leaf if leaf.is_a?(Lutaml::Xml::Schema::Xsd::Element)
|
|
231
255
|
end
|
|
232
256
|
end
|
|
233
257
|
|
|
234
|
-
|
|
258
|
+
walk_complex_content(node, &) if
|
|
259
|
+
node.is_a?(Lutaml::Xml::Schema::Xsd::ComplexType)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def walk_complex_content(node, &)
|
|
263
|
+
content = node.complex_content
|
|
264
|
+
return unless content
|
|
235
265
|
|
|
236
266
|
%i[extension restriction].each do |kind|
|
|
237
|
-
part =
|
|
267
|
+
part = content.public_send(kind)
|
|
238
268
|
each_particle_element(part, &) if part
|
|
239
269
|
end
|
|
240
270
|
end
|
|
@@ -157,15 +157,15 @@ module Lutaml
|
|
|
157
157
|
|
|
158
158
|
# Check if rule should run for given configuration
|
|
159
159
|
#
|
|
160
|
+
# Category filtering is not implemented in the configuration yet;
|
|
161
|
+
# only the enable flag applies.
|
|
162
|
+
#
|
|
160
163
|
# @param config [ValidationConfiguration] The configuration
|
|
161
164
|
# @return [Boolean]
|
|
162
|
-
def applicable?(
|
|
165
|
+
def applicable?(_config)
|
|
163
166
|
return false unless enabled?
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
return true unless config.respond_to?(:category_enabled?)
|
|
167
|
-
|
|
168
|
-
config.category_enabled?(category)
|
|
168
|
+
true
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
# Get option value
|
data/lib/lutaml/xsd/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.1
|
|
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-10-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: liquid
|