lutaml-xsd 1.1.13 → 1.2.0
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/.gitignore +0 -1
- data/CLAUDE.md +18 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +327 -0
- data/Rakefile +78 -1
- data/TODO.VISION.md +123 -0
- data/TODO.cleanup.md +43 -0
- data/TODO.conformance.md +99 -0
- data/TODO.docs-spa.md +46 -0
- data/TODO.interop.md +52 -0
- data/TODO.performance.md +80 -0
- data/TODO.som.md +47 -0
- data/benchmark/engines.rb +77 -0
- data/config/validation.yml +4 -0
- data/docs/CONFORMANCE.adoc +43 -0
- data/docs/JSON_SCHEMA.adoc +42 -0
- data/docs/superpowers/plans/2026-10-20-moxml-leptris-migration.md +735 -0
- data/docs/superpowers/specs/2026-10-20-moxml-leptris-migration-design.md +169 -0
- data/docs/superpowers/specs/2026-10-21-rule-engine-wiring-design.md +84 -0
- data/lib/lutaml/xsd/conformance/runner.rb +169 -0
- data/lib/lutaml/xsd/json_schema_exporter.rb +240 -0
- data/lib/lutaml/xsd/package_builder.rb +2 -2
- data/lib/lutaml/xsd/spa/schema_serializer.rb +12 -7
- data/lib/lutaml/xsd/validation/engine_context.rb +190 -0
- data/lib/lutaml/xsd/validation/rule_engine.rb +36 -18
- data/lib/lutaml/xsd/validation/rule_registry.rb +34 -0
- data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +126 -17
- data/lib/lutaml/xsd/validation/rules/content_model_validation_rule.rb +304 -194
- data/lib/lutaml/xsd/validation/rules/element_structure_rule.rb +25 -15
- data/lib/lutaml/xsd/validation/rules/identity_constraints_rule.rb +134 -0
- data/lib/lutaml/xsd/validation/rules/occurrence_validation_rule.rb +76 -25
- data/lib/lutaml/xsd/validation/rules/type_validation_rule.rb +134 -17
- data/lib/lutaml/xsd/validation/schema_definition_resolver.rb +254 -0
- data/lib/lutaml/xsd/validation/validation_configuration.rb +1 -0
- data/lib/lutaml/xsd/validation/validation_job.rb +70 -3
- data/lib/lutaml/xsd/validation/validation_rule.rb +44 -5
- data/lib/lutaml/xsd/validation/validator.rb +2 -1
- data/lib/lutaml/xsd/validation/xml_element.rb +2 -2
- data/lib/lutaml/xsd/version.rb +1 -1
- data/lib/lutaml/xsd.rb +14 -0
- data/lutaml-xsd.gemspec +2 -2
- metadata +27 -8
data/TODO.conformance.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# TODO.conformance — measured validation conformance (W3C XSD Test Suite)
|
|
2
|
+
|
|
3
|
+
Goal: conformance is a number, not a feeling. Vendor the W3C XML Schema
|
|
4
|
+
test-suite categories, build the harness, publish the pass-rate, grow it.
|
|
5
|
+
|
|
6
|
+
## Baseline (2026-10-22, 20 cases / 42 checks)
|
|
7
|
+
|
|
8
|
+
Corpus: 20 case classes under `spec/conformance/cases/`, modelled on the
|
|
9
|
+
W3C XML Schema Test Suite category taxonomy — self-authored because the
|
|
10
|
+
canonical 2007 suite distribution is no longer served from w3.org (owner
|
|
11
|
+
can vendor a tarball later; provenance README goes with it).
|
|
12
|
+
|
|
13
|
+
| mode | pass-rate |
|
|
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%) |
|
|
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)
|
|
38
|
+
|
|
39
|
+
History: 2026-10-20 baseline 20/25 (80.0%) with pipeline == engine
|
|
40
|
+
(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%).
|
|
43
|
+
|
|
44
|
+
## Harness
|
|
45
|
+
|
|
46
|
+
- [x] Conformance corpus under `spec/conformance/cases/` (directory
|
|
47
|
+
convention: schema.xsd + valid-*.xml + invalid-*.xml)
|
|
48
|
+
- [x] `Lutaml::Xsd::Conformance::Runner`: discovery, per-mode
|
|
49
|
+
classification, pass-rate report (engine + production pipeline)
|
|
50
|
+
- [x] `rake conformance` → prints report and writes
|
|
51
|
+
`tmp/conformance_report.md`
|
|
52
|
+
- [x] Deliverable doc: `docs/CONFORMANCE.adoc` (methodology, baseline,
|
|
53
|
+
gap register pointer, how to add cases)
|
|
54
|
+
- [ ] Vendor a real W3C XSD TS subset — DEFERRED: upstream distribution
|
|
55
|
+
URLs are dead as of 2026-10-20; the owner supplies the corpus
|
|
56
|
+
tarball and a provenance/license README is added with it
|
|
57
|
+
- [ ] Harness run against the rule engine with registries explicitly
|
|
58
|
+
populated — DEFERRED: subsumed by the rule-registry wiring design
|
|
59
|
+
below
|
|
60
|
+
|
|
61
|
+
## Rule-engine wiring (done 2026-10-21)
|
|
62
|
+
|
|
63
|
+
- [x] Design note: `docs/superpowers/specs/2026-10-21-rule-engine-wiring-design.md`
|
|
64
|
+
- [x] Element-scoped dispatch contract: `ValidationRule#schema_argument`
|
|
65
|
+
(defaults to the resolved declaration; occurrence rule returns the
|
|
66
|
+
content-model particle) and `runs_without_schema?`
|
|
67
|
+
- [x] `SchemaDefinitionResolver`: top-level + inline element index with
|
|
68
|
+
namespace matching and named-type resolution
|
|
69
|
+
- [x] `RuleRegistry.default(config)`: production rule set, feature-gated;
|
|
70
|
+
shared by `Validator` and the conformance harness
|
|
71
|
+
- [x] Rules resolve `type="prefix:Name"` references via the repository
|
|
72
|
+
- [x] Substitution groups: content-model and occurrence matching are
|
|
73
|
+
substitution-aware (substitution case now passes in pipeline mode)
|
|
74
|
+
- [x] Facet-collection handling fixed (empty arrays were treated as
|
|
75
|
+
present facets; `XmlAttribute` namespace ported to `.uri`)
|
|
76
|
+
- [x] Integration specs prove rule findings fire with the engine phase
|
|
77
|
+
disabled (ordering, missing content) and valid instances stay clean
|
|
78
|
+
|
|
79
|
+
## Rule-engine depth (remaining)
|
|
80
|
+
|
|
81
|
+
- [x] Identity constraints: `key`, `keyref`, `unique` —
|
|
82
|
+
`IdentityConstraintsRule` (selector/field XPath evaluation,
|
|
83
|
+
duplicate detection, keyref resolution); dangling-keyref activation
|
|
84
|
+
waits on lutaml-model `refer` mapping
|
|
85
|
+
- [x] Wildcards: `any` namespace constraints (##any/##other/##local/
|
|
86
|
+
##targetNamespace/lists) + `processContents` strict declaration
|
|
87
|
+
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
|
|
93
|
+
- [x] `xsi:nil` (nillable + empty-content checks) and `xsi:type`
|
|
94
|
+
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)
|
|
98
|
+
- [ ] XSD 1.1 constructs behind a version flag — DEFERRED: 1.1 strategy
|
|
99
|
+
decision needed (see TODO.som.md)
|
data/TODO.docs-spa.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# TODO.docs-spa — comprehension SOTA: interactive, live-validated documentation
|
|
2
|
+
|
|
3
|
+
Goal: leapfrog static commercial doc-generators (Oxygen/Altova) with an
|
|
4
|
+
interactive corpus browser where the documentation *executes*.
|
|
5
|
+
|
|
6
|
+
Status 2026-10-20: DEFERRED as a pillar. Reason: the SPA features below
|
|
7
|
+
need the frontend development loop (`rake build_frontend`, Vue dev server)
|
|
8
|
+
and a backend seam decision (CLI vs HTTP). The validation core these
|
|
9
|
+
features depend on is ready (EngineContext + `engine_validation` +
|
|
10
|
+
Conformance runner), so the UI work can start against a stable base. The
|
|
11
|
+
one prerequisite wiring — rule-registry gaps surfacing as findings — is
|
|
12
|
+
tracked in TODO.conformance.md.
|
|
13
|
+
|
|
14
|
+
## Live validation playground (the killer feature)
|
|
15
|
+
|
|
16
|
+
- [ ] SPA page: paste/upload an XML instance → validate against the loaded
|
|
17
|
+
package (engine + rule stack via a CLI/HTTP seam) → findings with
|
|
18
|
+
line/column/path — DEFERRED: backend seam decision + frontend build
|
|
19
|
+
loop
|
|
20
|
+
- [ ] Click-through: finding → schema component page — DEFERRED (needs
|
|
21
|
+
the playground first)
|
|
22
|
+
- [ ] Instance tree view with per-node validity — DEFERRED (needs
|
|
23
|
+
element-path findings in the UI; engine already reports paths)
|
|
24
|
+
|
|
25
|
+
## Navigation & comprehension
|
|
26
|
+
|
|
27
|
+
- [ ] Global search across types/elements/attributes/groups — DEFERRED:
|
|
28
|
+
the search index exists (`build_index`); ranking design + UI work
|
|
29
|
+
- [ ] Usage navigation matrix for attributes and groups — DEFERRED:
|
|
30
|
+
liquid methods pattern exists; extend `used_by` coverage first
|
|
31
|
+
- [ ] Type hierarchy graph in the SPA — DEFERRED (pairs with TODO.som.md)
|
|
32
|
+
- [ ] Dependency graph page — DEFERRED
|
|
33
|
+
|
|
34
|
+
## Evolution
|
|
35
|
+
|
|
36
|
+
- [ ] Schema diff between package versions — DEFERRED: structural diff
|
|
37
|
+
design needed (component identity across versions)
|
|
38
|
+
- [ ] Coverage view wired to `coverage_analyzer` — DEFERRED
|
|
39
|
+
|
|
40
|
+
## Polish
|
|
41
|
+
|
|
42
|
+
- [ ] SPA generation performance on large corpora — DEFERRED until the
|
|
43
|
+
post-recalibration corpus exists (TODO.performance.md)
|
|
44
|
+
- [ ] Accessibility pass on the SPA — DEFERRED (rides the next UI change)
|
|
45
|
+
- [ ] Nightly demo package regeneration in CI — DEFERRED: cimas-owned
|
|
46
|
+
workflow; needs an owner decision on schedule
|
data/TODO.interop.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# TODO.interop — bridges: RNG, JSON Schema, LutaML cross-model
|
|
2
|
+
|
|
3
|
+
Goal: no schema format is an island; every bridge lands in the LutaML
|
|
4
|
+
model-driven documentation pipeline.
|
|
5
|
+
|
|
6
|
+
Status 2026-10-20: RNG bridge migrated to Moxml; JSON Schema exporter
|
|
7
|
+
delivered; remaining items deferred with reasons below.
|
|
8
|
+
|
|
9
|
+
## RelaxNG bridge (rng gem, ~/src/lutaml/rng — its own repo + PR flow)
|
|
10
|
+
|
|
11
|
+
- [x] Migrate rng gem to Moxml (rng PR #35): five Nokogiri files ported
|
|
12
|
+
(rnc_to_rng_converter, rnc_parser, test_suite_parser,
|
|
13
|
+
schema_validator, external_ref_resolver); `nokogiri` removed from
|
|
14
|
+
the runtime dependencies, `moxml` added; dev Gemfile runs leptris
|
|
15
|
+
- [x] Round-trip coverage: the rng suite's RNC ↔ RNG round-trip and
|
|
16
|
+
spec-test suites pass on the Moxml converter (1056 examples; two
|
|
17
|
+
environmental timing guards were widened to pathological-regression
|
|
18
|
+
budgets — they also failed on pre-migration main on loaded machines)
|
|
19
|
+
- [x] rng 0.3.9 baseline recalibration (2026-10-21): converter expectations
|
|
20
|
+
re-baselined against the fixed include processor — complex types
|
|
21
|
+
170, simple types 13, elements 203, groups 237, attribute_groups
|
|
22
|
+
44; sections sequence: 7 choices / 1 group / 1 element (`sections`
|
|
23
|
+
present via bsi's override, trace in rng PR #34)
|
|
24
|
+
- [x] `rng ~> 0.3.9` pinned in lutaml-xsd (2026-10-21)
|
|
25
|
+
|
|
26
|
+
## JSON Schema bridge
|
|
27
|
+
|
|
28
|
+
- [x] XSD → JSON Schema (draft 2020-12) exporter:
|
|
29
|
+
`Lutaml::Xsd::JsonSchemaExporter` — objects from complexTypes,
|
|
30
|
+
properties/required from sequences and attributes, arrays from
|
|
31
|
+
repeated particles, enum/pattern/length/range facets, builtin type
|
|
32
|
+
mapping, `const` for fixed values, nillable → null union
|
|
33
|
+
- [x] Loss table documented in docs/JSON_SCHEMA.adoc (XSD regex dialect,
|
|
34
|
+
identity constraints, substitution groups, derivation flattening,
|
|
35
|
+
wildcards, defaults, mixed content, XSD 1.1)
|
|
36
|
+
- [ ] SPA "export JSON Schema" action per package — DEFERRED: needs the
|
|
37
|
+
SPA work in TODO.docs-spa.md
|
|
38
|
+
|
|
39
|
+
## LutaML cross-model (the differentiator)
|
|
40
|
+
|
|
41
|
+
- [ ] XSD → LutaML model export — DEFERRED: there is no cross-model
|
|
42
|
+
contract in this repo yet (LutaML class/association export needs a
|
|
43
|
+
design note agreed with the lutaml-model owners); the exporter
|
|
44
|
+
here is the template for that work
|
|
45
|
+
- [ ] Metanorma integration for schema-package documentation — DEFERRED:
|
|
46
|
+
depends on the SPA data contract being frozen
|
|
47
|
+
|
|
48
|
+
## Adjacent (evaluate, don't commit)
|
|
49
|
+
|
|
50
|
+
- [ ] ISO Schematron rule-engine plugin — DEFERRED: evaluate demand;
|
|
51
|
+
the rule_registry pattern fits, and the conformance harness gives
|
|
52
|
+
the measurement hook when it is taken up
|
data/TODO.performance.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# TODO.performance — measured speed on the fastest Ruby XML engine
|
|
2
|
+
|
|
3
|
+
Goal: stay the fastest, prove it continuously, and adopt every new engine
|
|
4
|
+
surface as moxml/leptris ship it.
|
|
5
|
+
|
|
6
|
+
## Engine integration maturation
|
|
7
|
+
|
|
8
|
+
- [x] Track https://github.com/lutaml/moxml/issues/410 — FIXED and
|
|
9
|
+
RELEASED (moxml 0.5.149, moxml PR #411). lutaml-xsd now locks
|
|
10
|
+
0.5.149: `Schema#content_valid?` answers correctly on leptris and
|
|
11
|
+
the engine content-seam specs run live.
|
|
12
|
+
- [x] Content delegation verified live: engine answers true/false for
|
|
13
|
+
matching/mismatching child sequences (engine_context_spec + the
|
|
14
|
+
formerly gated content-model rule specs).
|
|
15
|
+
- [ ] Adopt moxml `Plan`/`StructPlan` materialization for the hot
|
|
16
|
+
parse path if lutaml-model exposes seams for it (profile first)
|
|
17
|
+
- [ ] Engine error payload richness: once line/column/path are stable
|
|
18
|
+
across adapters, surface them in CLI output and the SPA findings UI
|
|
19
|
+
— PARTIAL: CLI JSON already carries code/message/severity/
|
|
20
|
+
location/line_number/context (verified 2026-10-21); SPA wiring
|
|
21
|
+
rides TODO.docs-spa.md
|
|
22
|
+
|
|
23
|
+
## Benchmarks
|
|
24
|
+
|
|
25
|
+
- [x] In-repo benchmark suite (`benchmark/engines.rb` + `rake
|
|
26
|
+
benchmark:engines`): engine parse/xpath/serialize, pipeline parse,
|
|
27
|
+
schema to_xml, compiled-schema validation
|
|
28
|
+
- [x] Baseline table: leptris vs nokogiri adapter (2026-10-20, Ruby 3.4.8,
|
|
29
|
+
arm64-darwin, 10.8 KB corpus converted from
|
|
30
|
+
metanorma-model-iso-grammars/bsi.rnc under rng 0.3.7 — corpus is
|
|
31
|
+
SMALL because rng 0.3.7 partially resolves the include tree;
|
|
32
|
+
re-run after the rng 0.3.9 recalibration for the full corpus):
|
|
33
|
+
|
|
34
|
+
| measurement | leptris | nokogiri |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| engine_parse | 0.3 ms | 0.4 ms |
|
|
37
|
+
| engine_xpath_elements | 0.9 ms | 0.4 ms |
|
|
38
|
+
| engine_serialize | 0.7 ms | 0.1 ms |
|
|
39
|
+
| pipeline_schema_parse | 274 ms | 377 ms |
|
|
40
|
+
| pipeline_schema_to_xml | 224 ms | 223 ms |
|
|
41
|
+
| engine_xsd_compile | 0.1 ms | n/a (SyntaxError on rng-0.3.7 corpus) |
|
|
42
|
+
| engine_xsd_validate | 0.0 ms | n/a |
|
|
43
|
+
|
|
44
|
+
Caveat: per-op values are noisy run-to-run (0.1–12 ms spread observed)
|
|
45
|
+
on a small corpus; the harness is the deliverable, numbers firm up on
|
|
46
|
+
the post-recalibration corpus.
|
|
47
|
+
- [ ] CI-optional benchmark run (manual dispatch) with trend notes —
|
|
48
|
+
no PR-blocking perf gates yet
|
|
49
|
+
|
|
50
|
+
## Profiling targets
|
|
51
|
+
|
|
52
|
+
- [ ] SPA generation memory profile on 100+ schema corpora — DEFERRED:
|
|
53
|
+
needs the post-recalibration corpus (TODO.cleanup.md) and the SPA
|
|
54
|
+
pillar (TODO.docs-spa.md); the marshal-cleanup code is already in
|
|
55
|
+
place
|
|
56
|
+
- [ ] `EngineContext.compile` cost on multi-schema repositories —
|
|
57
|
+
DEFERRED: measured at 0.1 ms/compile on the benchmark corpus (well
|
|
58
|
+
below noise); revisit only when the full-include corpus changes the
|
|
59
|
+
picture
|
|
60
|
+
- [ ] XmlNavigator walk cost on deep instance trees — DEFERRED: no
|
|
61
|
+
evidence of a hotspot yet; take it with the rule-engine wiring,
|
|
62
|
+
which is the walker's main consumer
|
|
63
|
+
|
|
64
|
+
## moxml Builder DSL profile (2026-10-21, reported to moxml sota/04)
|
|
65
|
+
|
|
66
|
+
rng's converter drives `Moxml::Builder`; the same 500-define synthetic
|
|
67
|
+
tree through the same DSL:
|
|
68
|
+
|
|
69
|
+
| path | time |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Moxml Builder + nokogiri adapter | 0.059 s |
|
|
72
|
+
| Moxml Builder + leptris adapter | 0.463 s |
|
|
73
|
+
| direct `Leptris::XML::Builder` | 0.007 s |
|
|
74
|
+
|
|
75
|
+
The ~8x gap is in moxml's leptris builder-face interaction (create_tag /
|
|
76
|
+
method_missing / face flush), not the binding — the DSL through the
|
|
77
|
+
nokogiri adapter is only ~2x native Nokogiri's builder. moxml's scoreboard
|
|
78
|
+
covers the mutation API, not the Builder DSL; this is offered as a row +
|
|
79
|
+
profile lead for the moxml perf branch. Work belongs in moxml, not here.
|
|
80
|
+
|
data/TODO.som.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# TODO.som — schema object model completeness (XSOM parity)
|
|
2
|
+
|
|
3
|
+
Goal: the Ruby SOM must answer every structural question XSOM can, with
|
|
4
|
+
XSD 1.1 constructs layered on.
|
|
5
|
+
|
|
6
|
+
Status 2026-10-20: DEFERRED as a pillar. Reason: every item here is a
|
|
7
|
+
specification-parity design cycle (component resolution semantics,
|
|
8
|
+
derivation graphs, 1.1 constructs), and the conformance gap register
|
|
9
|
+
(rule-engine wiring first) and the release-gated rng recalibration are
|
|
10
|
+
higher-leverage prerequisites. Track this file as the design backlog; do
|
|
11
|
+
not start mid-cycle.
|
|
12
|
+
|
|
13
|
+
## Parse-side completeness
|
|
14
|
+
|
|
15
|
+
- [ ] `xs:redefine` semantics (redefinition chains, component shadowing)
|
|
16
|
+
— DEFERRED: needs a component-resolution design note first
|
|
17
|
+
- [ ] Chameleon includes (namespace adoption rules) — DEFERRED: verify
|
|
18
|
+
current behavior against the rng include fixes, then design
|
|
19
|
+
- [ ] Substitution group closure and membership enumeration on the model
|
|
20
|
+
— DEFERRED: pairs with the conformance substitution-group gap
|
|
21
|
+
- [ ] `xs:group` / `xs:attributeGroup` reference resolution including
|
|
22
|
+
redefined groups — DEFERRED (same design cycle)
|
|
23
|
+
- [ ] Complex type derivation graph: extension/restriction chains,
|
|
24
|
+
`{base type definition}` reachability — DEFERRED: the type-hierarchy
|
|
25
|
+
analyzer exists; extending it into a full derivation graph is the
|
|
26
|
+
design entry point
|
|
27
|
+
- [ ] Simple type facets carried losslessly — DEFERRED: covered
|
|
28
|
+
opportunistically by the JSON Schema exporter tests; a dedicated
|
|
29
|
+
round-trip matrix should come with the recalibration
|
|
30
|
+
|
|
31
|
+
## Query surfaces
|
|
32
|
+
|
|
33
|
+
- [ ] Structural queries via moxml XPath where trees are involved —
|
|
34
|
+
DEFERRED: profile first (EngineContext already avoids Ruby walks)
|
|
35
|
+
- [ ] `SchemaRepository#types_in_namespace` deterministic ordering and
|
|
36
|
+
stable public docs — DEFERRED: small, take it with the next
|
|
37
|
+
repository-touching PR
|
|
38
|
+
- [ ] Type hierarchy export (DOT/mermaid) into the SPA — DEFERRED: needs
|
|
39
|
+
the SPA work in TODO.docs-spa.md
|
|
40
|
+
|
|
41
|
+
## 1.1 constructs (behind `version: "1.1"`)
|
|
42
|
+
|
|
43
|
+
- [ ] `xs:assert` / `xs:assertion` model + evaluation hooks — DEFERRED:
|
|
44
|
+
XSD 1.1 strategy decision needed (engine support vs pure Ruby)
|
|
45
|
+
- [ ] `xs:alternative` conditional types; `openContent` modes — DEFERRED
|
|
46
|
+
(same 1.1 strategy)
|
|
47
|
+
- [ ] `xs:override` and `xs:defaultOpenContent` — DEFERRED (same)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Engine benchmark: leptris vs nokogiri through Moxml, on a real corpus.
|
|
4
|
+
#
|
|
5
|
+
# ADAPTER=leptris bundle exec ruby benchmark/engines.rb
|
|
6
|
+
#
|
|
7
|
+
# Per-adapter numbers land on stdout as `key: value` lines; the rake task
|
|
8
|
+
# (`rake benchmark:engines`) runs both adapters for side-by-side recording
|
|
9
|
+
# in TODO.performance.md.
|
|
10
|
+
|
|
11
|
+
require "benchmark"
|
|
12
|
+
require "tmpdir"
|
|
13
|
+
require "fileutils"
|
|
14
|
+
|
|
15
|
+
ADAPTER = ENV.fetch("ADAPTER", "leptris")
|
|
16
|
+
|
|
17
|
+
require "rng" if ADAPTER == "leptris" # load first so Moxml picks it
|
|
18
|
+
require "lutaml/xsd"
|
|
19
|
+
|
|
20
|
+
def corpus_path
|
|
21
|
+
corpus = File.join(__dir__, "..", "tmp", "bench_corpus.xsd")
|
|
22
|
+
unless File.exist?(corpus)
|
|
23
|
+
rnc = File.expand_path(
|
|
24
|
+
"../spec/fixtures/metanorma-model-iso-grammars/bsi.rnc", __dir__
|
|
25
|
+
)
|
|
26
|
+
grammar = Rng.parse_file(rnc)
|
|
27
|
+
schema = Lutaml::Xsd::RngToXsdConverter.new(grammar, file_path: rnc).convert
|
|
28
|
+
FileUtils.mkdir_p(File.dirname(corpus))
|
|
29
|
+
File.write(corpus, schema.to_xml)
|
|
30
|
+
end
|
|
31
|
+
corpus
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def measure(label, iterations: 5, &)
|
|
35
|
+
gc_was = GC.enable
|
|
36
|
+
GC.start
|
|
37
|
+
elapsed = Benchmark.realtime { iterations.times(&) }
|
|
38
|
+
GC.start if gc_was
|
|
39
|
+
format("%-34s %8.1f ms/op", label, (elapsed / iterations) * 1000)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
corpus = File.read(corpus_path)
|
|
43
|
+
ctx = Moxml::Context.new(ADAPTER.to_sym)
|
|
44
|
+
|
|
45
|
+
puts "adapter: #{ADAPTER}"
|
|
46
|
+
puts "corpus_bytes: #{corpus.bytesize}"
|
|
47
|
+
|
|
48
|
+
# Engine-level parse
|
|
49
|
+
doc = nil
|
|
50
|
+
puts measure("engine_parse") { doc = ctx.parse(corpus) }
|
|
51
|
+
|
|
52
|
+
# Engine-level XPath
|
|
53
|
+
puts measure("engine_xpath_elements") { doc.xpath("//*[local-name() = 'element']") }
|
|
54
|
+
|
|
55
|
+
# Engine-level serialize
|
|
56
|
+
puts measure("engine_serialize") { doc.to_xml }
|
|
57
|
+
|
|
58
|
+
# Schema pipeline parse (lutaml-model over Moxml)
|
|
59
|
+
Lutaml::Xml::Schema::Xsd.parse(corpus) # warm autoloads/registration
|
|
60
|
+
schema = nil
|
|
61
|
+
puts measure("pipeline_schema_parse") { schema = Lutaml::Xml::Schema::Xsd.parse(corpus) }
|
|
62
|
+
|
|
63
|
+
# Schema model serialization
|
|
64
|
+
puts measure("pipeline_schema_to_xml") { schema.to_xml }
|
|
65
|
+
|
|
66
|
+
# Compiled-schema instance validation (tier-1 engines)
|
|
67
|
+
instance = <<~XML
|
|
68
|
+
<bible xmlns="http://example.com/bsi"><content><section-title>t</section-title></content></bible>
|
|
69
|
+
XML
|
|
70
|
+
begin
|
|
71
|
+
compiled = ctx.xsd(corpus)
|
|
72
|
+
puts measure("engine_xsd_compile", iterations: 3) { ctx.xsd(corpus) }
|
|
73
|
+
puts measure("engine_xsd_validate") { compiled.validate(instance) }
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
puts "engine_xsd_compile n/a (#{e.class})"
|
|
76
|
+
puts "engine_xsd_validate n/a (#{e.class})"
|
|
77
|
+
end
|
data/config/validation.yml
CHANGED
|
@@ -48,6 +48,10 @@ validation:
|
|
|
48
48
|
# Validate namespace declarations
|
|
49
49
|
validate_namespaces: true
|
|
50
50
|
|
|
51
|
+
# Engine-native XSD validation (Moxml::XSD) when the adapter
|
|
52
|
+
# provides an engine (leptris/nokogiri); findings are additive
|
|
53
|
+
engine_validation: true
|
|
54
|
+
|
|
51
55
|
# Error reporting configuration
|
|
52
56
|
error_reporting:
|
|
53
57
|
# Include XPath location in error messages
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Conformance suite
|
|
2
|
+
|
|
3
|
+
`spec/conformance/cases/` holds one directory per case class, modelled on
|
|
4
|
+
the W3C XML Schema Test Suite category taxonomy. The canonical 2007 suite
|
|
5
|
+
distribution is no longer served from w3.org (checked 2026-10-20); cases
|
|
6
|
+
here are original, and a vendored upstream subset can be added later with
|
|
7
|
+
a provenance + license README alongside it.
|
|
8
|
+
|
|
9
|
+
## Case layout
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
spec/conformance/cases/<case-name>/
|
|
13
|
+
schema.xsd # the schema under test
|
|
14
|
+
valid-*.xml # instances that MUST validate
|
|
15
|
+
invalid-*.xml # instances that MUST NOT validate
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Running
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle exec rake conformance
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Prints a per-mode report and writes `tmp/conformance_report.md`.
|
|
25
|
+
|
|
26
|
+
Two modes are measured independently:
|
|
27
|
+
|
|
28
|
+
- **engine** — `Moxml::XSD` tier-1 validation through `EngineContext`
|
|
29
|
+
(leptris when the adapter ships an engine)
|
|
30
|
+
- **pipeline** — the production `Validator` stack (`ValidationJob` with
|
|
31
|
+
the default rule registry)
|
|
32
|
+
|
|
33
|
+
## Baseline and gap register
|
|
34
|
+
|
|
35
|
+
Recorded in `TODO.conformance.md` (2026-10-20: 20/25 = 80.0% in both
|
|
36
|
+
modes). Each failing check maps to a tracked gap; the gap register is the
|
|
37
|
+
work queue for the conformance pillar.
|
|
38
|
+
|
|
39
|
+
## Adding a case
|
|
40
|
+
|
|
41
|
+
Create the directory, add `schema.xsd`, at least one `valid-*.xml` and one
|
|
42
|
+
`invalid-*.xml`, and re-run the rake task. No registration needed — the
|
|
43
|
+
runner discovers directories containing `schema.xsd`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# JSON Schema export
|
|
2
|
+
|
|
3
|
+
`Lutaml::Xsd::JsonSchemaExporter` writes an XSD schema as a JSON Schema
|
|
4
|
+
2020-12 document.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
schema = Lutaml::Xml::Schema::Xsd.parse(File.read("schema.xsd"))
|
|
8
|
+
json = Lutaml::Xsd::JsonSchemaExporter.new(schema).to_json
|
|
9
|
+
File.write("schema.json", json)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What maps
|
|
13
|
+
|
|
14
|
+
- top-level `complexType`/`simpleType`/`element` declarations → `$defs`
|
|
15
|
+
- `sequence` / `all` elements → `properties`; `minOccurs > 0` → `required`
|
|
16
|
+
- `maxOccurs > 1` / `unbounded` → `{"type": "array", "items": ...}`
|
|
17
|
+
- `choice` and top-level elements → `oneOf`
|
|
18
|
+
- attributes → `properties`; `use="required"` → `required`; `fixed` → `const`
|
|
19
|
+
- `simpleType` restrictions: `enumeration` → `enum`; `pattern` → `pattern`;
|
|
20
|
+
length/range facets → `minLength`/`maxLength`/`minimum`/`maximum`/
|
|
21
|
+
`exclusiveMinimum`/`exclusiveMaximum`
|
|
22
|
+
- built-in types to JSON types/formats (date → `format: date`, dateTime →
|
|
23
|
+
`date-time`, time → `time`, anyURI → `uri`, integer family → `integer`,
|
|
24
|
+
decimal/float/double → `number`)
|
|
25
|
+
- `nillable="true"` → the JSON type accepts `null`
|
|
26
|
+
|
|
27
|
+
## Loss table (deliberate)
|
|
28
|
+
|
|
29
|
+
| XSD | JSON Schema outcome |
|
|
30
|
+
|---|---|
|
|
31
|
+
| XSD regular expressions | emitted verbatim; XSD regex dialect differs from ECMA-262 — patterns may over/under-match |
|
|
32
|
+
| `key` / `keyref` / `unique` identity constraints | not representable; dropped |
|
|
33
|
+
| substitution groups | members are separate `$defs`; head/member relation is not encoded |
|
|
34
|
+
| complex-type derivation (extension/restriction) | flattened into the derived type; base linkage lost |
|
|
35
|
+
| `anyAttribute` / open content | not represented (`additionalProperties: false` is emitted) |
|
|
36
|
+
| wildcards (`xs:any`) | not represented |
|
|
37
|
+
| default values | not emitted (defaults are instance-processing semantics) |
|
|
38
|
+
| mixed content | modelled as element properties only; interleaved text is not captured |
|
|
39
|
+
| XSD 1.1 assertions / alternatives / openContent | not represented |
|
|
40
|
+
|
|
41
|
+
The exporter targets documentation, tooling interop, and contract
|
|
42
|
+
publication; it is not a validation-equivalence layer.
|