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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 735a8e1b0b021976b9a2030dc9835c262bb5db7aa8774e20849a237b7f413d7a
4
- data.tar.gz: e0500a64f394396c1d5d02f2725eaaccfe4d14de51a1d18a0cb39078dc3f58c4
3
+ metadata.gz: c78737c6dd26a5aa2ab915675b5fc5ce6316590d776d9ef6d5947988ef804adf
4
+ data.tar.gz: 56dd63144161aa1f7065067fcc74cf434bf9d40e1e1059c7e48e3cd99da00124
5
5
  SHA512:
6
- metadata.gz: 3559756dd36b8f19dfd056b2b352105a52958619748908da606611409d08da2db06ff55a7622c702c928a241c7c984271e8ed93f827902c6abea005d98fe33eb
7
- data.tar.gz: 69187d54a64c4e15115461ae932c13136bf28fbe2871ef277f9eaf1afcc2602381c923c82b104fac3c74d5cb94172368ec414d4b33e7a3a244e1cc98a6f45fd7
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.1.14)
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.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.0)
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,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 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
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
- %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)
@@ -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.respond_to?(:complex_types) || schema.respond_to?(:complex_type)
578
+ return [] unless schema.is_a?(Lutaml::Xml::Schema::Xsd::Schema)
579
579
 
580
- types = schema.respond_to?(:complex_types) ? schema.complex_types : schema.complex_type
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.respond_to?(:element) && schema.element
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.respond_to?(:complex_type) && element.complex_type
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.respond_to?(:abstract) ? element.complex_type.abstract : false,
619
- mixed: element.complex_type.respond_to?(:mixed) ? element.complex_type.mixed : false,
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 [] if !model.respond_to?(:choice) || !model.choice
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 [] if !model.respond_to?(:sequence) || !model.sequence
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.respond_to?(:groups) || schema.respond_to?(:group)
883
+ return [] unless schema.groups
884
884
 
885
- grps = schema.respond_to?(:groups) ? schema.groups : schema.group
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.respond_to?(:ref) ? model.ref : nil,
1085
- name: model.respond_to?(:name) ? model.name : nil,
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 [] if !model.respond_to?(:group) || !model.group
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 model (handles extension nesting)
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
- # Used for both direct attribute groups and those inside content model extensions.
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
- if model.respond_to?(:attribute_group) && model.attribute_group && !model.attribute_group.empty?
1123
- groups = model.attribute_group.is_a?(Array) ? model.attribute_group : [model.attribute_group]
1124
- refs.concat(groups)
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.respond_to?(:element)
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.respond_to?(:any_attribute)
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/element/
101
- # any/group particles plus, for extensions, the base type's
102
- # particles first. Cycle-guarded. Particles are kept whole —
103
- # nested containers are expanded by the walk's matching, which
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
- %i[sequence choice all element any group].each do |kind|
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.respond_to?(:complex_content) && type.complex_content
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
- end
139
- %i[sequence choice all element any group].each do |kind|
140
- next unless extension.respond_to?(kind)
141
-
142
- Array(extension.public_send(kind)).each do |particle|
143
- next unless particle
144
-
145
- if particle.is_a?(Lutaml::Xsd::Sequence)
146
- particles.concat(collect_particles(particle))
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
- if content_model.respond_to?(:resolved_element_order) &&
394
- content_model.resolved_element_order.to_a.any?
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
- xpaths = constraint.selector.respond_to?(:xpath) ? constraint.selector.xpath : constraint.selector
104
- Array(xpaths).map { |x| x.respond_to?(:xpath) ? x.xpath : x.to_s }
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).map do |field|
109
- field.respond_to?(:xpath) ? field.xpath : field.to_s
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
- first.respond_to?(:value) && first.value ? first.value : first.text
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
- PARTICLE_KINDS = %i[sequence choice all].freeze
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
- return nil unless type.respond_to?(:sequence) || type.respond_to?(:choice) || type.respond_to?(:all)
130
-
131
- direct = Array(type.sequence).first ||
132
- Array(type.choice).first || Array(type.all).first
133
- return direct if direct
134
-
135
- # Derived types keep their particles under complex_content.
136
- if type.respond_to?(:complex_content) && type.complex_content
137
- content = type.complex_content
138
- part = Array(content.extension).first ||
139
- Array(content.restriction).first
140
- return particle_for_type(part) if part
141
- end
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
- nil
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
- PARTICLE_KINDS.each do |kind|
220
- next unless node.respond_to?(kind) # optional particle collections
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
- next unless particle
224
-
225
- Array(particle.element).each(&)
226
- PARTICLE_KINDS.each do |nested|
227
- next unless particle.respond_to?(nested)
228
-
229
- each_particle_element(particle.public_send(nested), &)
230
- end
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
- return unless node.respond_to?(:complex_content) && node.complex_content
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 = node.complex_content.public_send(kind)
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?(config)
165
+ def applicable?(_config)
163
166
  return false unless enabled?
164
167
 
165
- # Check if category is enabled in config
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Xsd
5
- VERSION = "1.2.0"
5
+ VERSION = "1.2.1"
6
6
  end
7
7
  end
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.0
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-21 00:00:00.000000000 Z
11
+ date: 2026-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid