lutaml-xsd 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 735a8e1b0b021976b9a2030dc9835c262bb5db7aa8774e20849a237b7f413d7a
4
- data.tar.gz: e0500a64f394396c1d5d02f2725eaaccfe4d14de51a1d18a0cb39078dc3f58c4
3
+ metadata.gz: a22b97347b4ad22fc7698a047a1037f4b16c5a6bad3ed0154701908f0052ea29
4
+ data.tar.gz: 0a3d2bd48d987524cbdc427adefcdc128ebcd089a2ed5e25203c28ff27ba4416
5
5
  SHA512:
6
- metadata.gz: 3559756dd36b8f19dfd056b2b352105a52958619748908da606611409d08da2db06ff55a7622c702c928a241c7c984271e8ed93f827902c6abea005d98fe33eb
7
- data.tar.gz: 69187d54a64c4e15115461ae932c13136bf28fbe2871ef277f9eaf1afcc2602381c923c82b104fac3c74d5cb94172368ec414d4b33e7a3a244e1cc98a6f45fd7
6
+ metadata.gz: 304b7555580c9bd6b79464b3b2d9ff73d9c69177790fbcd8bb97b9b0438fdc96b19ce19bdbaba4a9a3c2c6446402e81ee457614cd3e33c3dac35e02b2c948d74
7
+ data.tar.gz: 39593e00b27fb41c0fe4e374b682c4172dabcf56cd8250e2917f76c135e9f6421e1c65eaca4c0c3896a7d03bae1395fa18c8a1b6f8e38bb3e23cbfa4e195ef6a
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lutaml-xsd (1.1.14)
4
+ lutaml-xsd (1.2.1)
5
5
  liquid (>= 4.0, < 6.0)
6
- lutaml-model (~> 0.8.114)
6
+ lutaml-model (~> 0.8.123)
7
7
  lutaml-store (~> 0.3)
8
8
  moxml (~> 0.5.147)
9
9
  paint (~> 2.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.107)
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.379.0-aarch64-linux)
49
+ leptris (1.9.380.2-aarch64-linux)
50
50
  ffi (~> 1.15)
51
- leptris (1.9.379.0-aarch64-linux-musl)
51
+ leptris (1.9.380.2-aarch64-linux-musl)
52
52
  ffi (~> 1.15)
53
- leptris (1.9.379.0-arm-linux)
53
+ leptris (1.9.380.2-arm-linux)
54
54
  ffi (~> 1.15)
55
- leptris (1.9.379.0-arm-linux-musl)
55
+ leptris (1.9.380.2-arm-linux-musl)
56
56
  ffi (~> 1.15)
57
- leptris (1.9.379.0-arm64-darwin)
57
+ leptris (1.9.380.2-arm64-darwin)
58
58
  ffi (~> 1.15)
59
- leptris (1.9.379.0-x64-mingw-ucrt)
59
+ leptris (1.9.380.2-x64-mingw-ucrt)
60
60
  ffi (~> 1.15)
61
- leptris (1.9.379.0-x86_64-darwin)
61
+ leptris (1.9.380.2-x86_64-darwin)
62
62
  ffi (~> 1.15)
63
- leptris (1.9.379.0-x86_64-linux)
63
+ leptris (1.9.380.2-x86_64-linux)
64
64
  ffi (~> 1.15)
65
- leptris (1.9.379.0-x86_64-linux-musl)
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.119)
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.150)
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.34.3-aarch64-linux)
185
+ yeptris (0.6.36.0-aarch64-linux)
186
186
  ffi (~> 1.15)
187
- yeptris (0.6.34.3-aarch64-linux-musl)
187
+ yeptris (0.6.36.0-aarch64-linux-musl)
188
188
  ffi (~> 1.15)
189
- yeptris (0.6.34.3-arm-linux-eabihf)
189
+ yeptris (0.6.36.0-arm-linux-eabihf)
190
190
  ffi (~> 1.15)
191
- yeptris (0.6.34.3-arm-linux-musleabihf)
191
+ yeptris (0.6.36.0-arm-linux-musleabihf)
192
192
  ffi (~> 1.15)
193
- yeptris (0.6.34.3-arm64-darwin)
193
+ yeptris (0.6.36.0-arm64-darwin)
194
194
  ffi (~> 1.15)
195
- yeptris (0.6.34.3-x64-mingw-ucrt)
195
+ yeptris (0.6.36.0-x64-mingw-ucrt)
196
196
  ffi (~> 1.15)
197
- yeptris (0.6.34.3-x86_64-darwin)
197
+ yeptris (0.6.36.0-x86_64-darwin)
198
198
  ffi (~> 1.15)
199
- yeptris (0.6.34.3-x86_64-linux)
199
+ yeptris (0.6.36.0-x86_64-linux)
200
200
  ffi (~> 1.15)
201
- yeptris (0.6.34.3-x86_64-linux-musl)
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.107) sha256=0dfac533189cf2a551423139529a17a7472f2cbc357954df3f59f7c0dddd3bfd
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.379.0-aarch64-linux) sha256=5905ebe2cf37de03f0f3849f9464426a68c589f67ef9fd1a1b6ec593802c4e04
255
- leptris (1.9.379.0-aarch64-linux-musl) sha256=414936329f97b5f0b779de33f87eb37bbede53368c0531bcdf6dd66459215d52
256
- leptris (1.9.379.0-arm-linux) sha256=c528b0ff5232952768ed313339422ea05ee131650b70121865a97204c99e8f8e
257
- leptris (1.9.379.0-arm-linux-musl) sha256=b2e1ead4323574d76bb061669cd993b10c6115f1e2c2dbc0f68ce1c13fd02ce2
258
- leptris (1.9.379.0-arm64-darwin) sha256=b5f492c8a72b1403a4438f491d7de005afc6c89a53044695db756f5e5b4e6eea
259
- leptris (1.9.379.0-x64-mingw-ucrt) sha256=d529b2f57df8f64ef43ae1430a429d850109427dc3a21725165bbe0f1f3c7c13
260
- leptris (1.9.379.0-x86_64-darwin) sha256=0056b0a40ac049cbe3cc4b3612724a0cef9ae562b9bd8ea533d59c1d4ad42b23
261
- leptris (1.9.379.0-x86_64-linux) sha256=76a20acff1b682ee32f73b2f39c2ee05b1ea312eb372c2b89b0c5f80afd35b6a
262
- leptris (1.9.379.0-x86_64-linux-musl) sha256=96f44ba6601fd8f921b4728027a461291e5b869f13784917451c9045ef9d2b80
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.119) sha256=e638b18536cd5a042df8e98e9d16893ee801461115e9398039887f7e0c616443
265
+ lutaml-model (0.8.123) sha256=475f391371965f5baee2a9d018a7c9da8361c3774d3802749aa8805ee1253fde
266
266
  lutaml-store (0.3.2) sha256=9c455dbab32b4c635c48a27213f6f56fda191731c4574eb6187184ba0588a672
267
- lutaml-xsd (1.1.14)
267
+ lutaml-xsd (1.2.1)
268
268
  memo_wise (1.13.0) sha256=30220c38c4cef410849bc73553c58664dc2c91c6379e4a1df22aea02358b716b
269
- moxml (0.5.150) sha256=78b9f0950a1e64c3b223ebcf31f311f756181036260798e9877509a6b78f6088
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.34.3-aarch64-linux) sha256=f21cf9e113578bf916ce7146cb62bc98e27909bf9a004315399670453fa69d3a
317
- yeptris (0.6.34.3-aarch64-linux-musl) sha256=9af54b9b80e4fda224d6b4715db3bd6c8ecc6ab55bb3f487156f4fe9a3c98551
318
- yeptris (0.6.34.3-arm-linux-eabihf) sha256=5c4403cbdcec942b506a57b33288bbd2c163da8378e2fb515f9cb602ac2b2d10
319
- yeptris (0.6.34.3-arm-linux-musleabihf) sha256=ab01d25165d62af20ef675ca342a52151fedaf214acf9fb4c15493cd3b425f7a
320
- yeptris (0.6.34.3-arm64-darwin) sha256=679888f46c24fc5992b42e447d593524d4aad2f3ebb5d6bfde17ee03e0aca5c0
321
- yeptris (0.6.34.3-x64-mingw-ucrt) sha256=67ee2e21e479e8ac11515bb61cb6ad0c6568c15e8bab727a31457b0ab94975ab
322
- yeptris (0.6.34.3-x86_64-darwin) sha256=1e888708d24c1565d78f4151a6fd2be97c132cd74f61b00fc373ef00587b0d23
323
- yeptris (0.6.34.3-x86_64-linux) sha256=e0846723912bc714d2ea6042f0575e6bf58ba420269c51115eebb3a7146945f5
324
- yeptris (0.6.34.3-x86_64-linux-musl) sha256=fc14c66e5779fa82506e89146bbfd809605eede1f2a16a4eb7a23c5b073d8101
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
- - [ ] `lib/lutaml/xsd/schema_query_service.rb.bak` (untracked) — DEFERRED,
23
- owner confirmation required: archive under `docs/archive/` or delete
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, moxml 0.5.150) | 35/42 (83.3%) |
16
- | pipeline (ValidationJob + wired rule engine) | 40/42 (95.2%) |
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 outperforms the engine by five checks: the root-declaration
19
- check catches a wrong-namespace root that engine root-matching silently
20
- skips; the identity-constraints rule catches duplicate keys the engine
21
- lacks; xsi:nil handling rejects non-nillable nils; strict wildcards
22
- (element and attribute) reject undeclared content. Zero false positives
23
- retained.
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 lifted the
42
- pipeline to 40/42 (95.2%).
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,20 +76,26 @@ 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 activation
84
- waits on lutaml-model `refer` mapping
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 where the parse layer exposes the
89
- declaration (attributeGroup, content-model restriction) —
90
- complexType-level and extension-shape anyAttribute mappings are
91
- missing upstream (lutaml-model), keeping those on pre-wildcard
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 acceptance needs engine-scope rework
96
- - [ ] Particle checking: UPA detection — DEFERRED (engine covers the
97
- tier-1 subset; UPA is schema-side checking)
90
+ override); the engine's valid-derived false positive is fixed in
91
+ leptris 1.9.380.2 (moxml#433)
92
+ - [x] Particle checking: UPA detection (2026-10-22) —
93
+ `UniqueParticleAttribution` schema-side checker: first-set
94
+ computation over sequence/choice/all with wildcard/element
95
+ overlap, choice-alternative and sequence-junction conflict rules,
96
+ container occurrence folding. Standalone API
97
+ (`findings` → upa_violation findings); zero findings on the
98
+ conformance corpus schemas (no false positives). Deferred depth:
99
+ xs:group expansion, substitution-group-aware first-sets
98
100
  - [ ] XSD 1.1 constructs behind a version flag — DEFERRED: 1.1 strategy
99
101
  decision needed (see TODO.som.md)
@@ -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
- %i[sequence choice all].each do |kind|
161
- particle = node.public_send(kind) if node.respond_to?(kind)
162
- next unless particle
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
- Array(particle.element).each(&)
165
- Array(particle.choice).each { |choice| each_element_particle(choice, &) }
166
- Array(particle.sequence).each { |seq| each_element_particle(seq, &) }
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
- return unless node.complex_content
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 = node.complex_content.public_send(kind)
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
- patterns = []
67
- pattern_types.each do |attr|
68
- next unless container.respond_to?(attr)
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
- # The attribute names that hold pattern children
84
- def pattern_types
85
- %w[element ref choice group interleave mixed optional zeroOrMore
86
- oneOrMore text empty value data list notAllowed attribute]
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.respond_to?(:documentation) && rng_elem.documentation
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.respond_to?(:documentation) && rng_attr.documentation
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
- pattern_types.each do |type_name|
1153
- next unless attr.respond_to?(type_name)
1154
-
1155
- child = attr.send(type_name)
1156
- return child if child && !child.is_a?(Array) && !child.is_a?(Lutaml::Model::UninitializedClass)
1157
- end
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
- name = node.respond_to?(:attr_name) ? node.attr_name : nil
1164
- if (name.nil? || name.empty?) && node.respond_to?(:name)
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.respond_to?(:value)
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
- if has_non_element && result.respond_to?(:min_occurs=)
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 child.respond_to?(:min_occurs=)
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].respond_to?(:name) ? result[:element].name : name
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.respond_to?(:element) && schema.element
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.respond_to?(:complex_type) && element.complex_type
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)