moxml 0.5.161 → 0.5.163
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/benchmark/README.md +10 -10
- data/benchmark/scoreboard.json +33 -33
- data/docs/_pages/model-coverage.adoc +1 -0
- data/docs/_pages/performance.adoc +11 -11
- data/docs/_references/processing-model.adoc +10 -4
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd/schema_rules.rb +174 -41
- data/lib/moxml/xsd.rb +9 -0
- data/spec/moxml/xsd_underdetection_spec.rb +171 -5
- data/spec/moxml/xsd_version_spec.rb +82 -0
- data/spec/support/model_coverage.rb +4 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66e9a76f3fb1a9a8e41d39d3f49c6ea97536a1d08a7aaf145169eb2ca8b2e72e
|
|
4
|
+
data.tar.gz: 5190677a144235ce163990b3cd3f7f6ef2a123ef4e02b1b81626e2467d74bcaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8dae5f5b85c572bf5f31efff0dcd1694854a5fffcf03feb066997136b53a44e2423a8208c08944c2159cd28ecdf83954cfe9856ed8354597d8ae8a664d7c434f
|
|
7
|
+
data.tar.gz: 0347c21ed0aecc388117e2c69632f9be715f0f1a073b5ac1dfad06fc8a0760cf07d652fc7cb39b0641ff5358b8a0d075f22de71befa72bdd687e24bf9ea1f2b4
|
data/benchmark/README.md
CHANGED
|
@@ -92,16 +92,16 @@ rewrites this block and `docs/_pages/performance.adoc`):
|
|
|
92
92
|
<!-- SCOREBOARD:BEGIN -->
|
|
93
93
|
| Row | Ratio | Kind | Status |
|
|
94
94
|
| --- | ----- | ---- | ------ |
|
|
95
|
-
| `parse` |
|
|
96
|
-
| `serialize` | 1.
|
|
97
|
-
| `xpath //field (count)` |
|
|
98
|
-
| `xpath //field (each)` | 0.
|
|
99
|
-
| `attr pairs listing` |
|
|
100
|
-
| `build add_element` | 0.
|
|
101
|
-
| `walk+read (warm)` | 0.
|
|
102
|
-
| `walk+read (cold)` | 0.
|
|
103
|
-
| `children recursion (cold)` | 0.
|
|
104
|
-
| `plan records (typed)` | 1.
|
|
95
|
+
| `parse` | 4.61x | parity | met |
|
|
96
|
+
| `serialize` | 1.65x | parity | met |
|
|
97
|
+
| `xpath //field (count)` | 3.39x | parity | met |
|
|
98
|
+
| `xpath //field (each)` | 0.34x | structural | tracked (below parity) |
|
|
99
|
+
| `attr pairs listing` | 8.48x | parity | met |
|
|
100
|
+
| `build add_element` | 0.74x | structural | tracked (below parity) |
|
|
101
|
+
| `walk+read (warm)` | 0.54x | structural | tracked (below parity) |
|
|
102
|
+
| `walk+read (cold)` | 0.37x | structural | tracked (below parity), paused: leptris-ruby#527 (1.9.380 crossing regression) |
|
|
103
|
+
| `children recursion (cold)` | 0.45x | structural | tracked (below parity), paused: leptris-ruby#527 (1.9.380 crossing regression) |
|
|
104
|
+
| `plan records (typed)` | 1.13x | data | informational |
|
|
105
105
|
<!-- SCOREBOARD:END -->
|
|
106
106
|
|
|
107
107
|
The open rows are the known structural gap: every node the facade
|
data/benchmark/scoreboard.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocol": {
|
|
3
|
-
"measured": "2026-10-
|
|
3
|
+
"measured": "2026-10-24T05:26:53Z",
|
|
4
4
|
"ruby": "3.3.13",
|
|
5
5
|
"adapter": "leptris",
|
|
6
6
|
"adapter_version": "1.9.381.0",
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
"reps": 9,
|
|
9
9
|
"warmup": 2,
|
|
10
10
|
"clock": "CLOCK_PROCESS_CPUTIME_ID",
|
|
11
|
-
"load": 1.
|
|
11
|
+
"load": 1.74
|
|
12
12
|
},
|
|
13
13
|
"rows": [
|
|
14
14
|
{
|
|
15
15
|
"row": "parse",
|
|
16
16
|
"kind": "parity",
|
|
17
17
|
"parity": true,
|
|
18
|
-
"baseline_us":
|
|
19
|
-
"candidate_us":
|
|
20
|
-
"ratio":
|
|
18
|
+
"baseline_us": 1075.7,
|
|
19
|
+
"candidate_us": 233.6,
|
|
20
|
+
"ratio": 4.606,
|
|
21
21
|
"baseline_allocs": 7,
|
|
22
22
|
"candidate_allocs": 18
|
|
23
23
|
},
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"row": "serialize",
|
|
26
26
|
"kind": "parity",
|
|
27
27
|
"parity": true,
|
|
28
|
-
"baseline_us":
|
|
29
|
-
"candidate_us":
|
|
30
|
-
"ratio": 1.
|
|
28
|
+
"baseline_us": 457.0,
|
|
29
|
+
"candidate_us": 277.6,
|
|
30
|
+
"ratio": 1.646,
|
|
31
31
|
"baseline_allocs": 30,
|
|
32
32
|
"candidate_allocs": 14
|
|
33
33
|
},
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"row": "xpath //field (count)",
|
|
36
36
|
"kind": "parity",
|
|
37
37
|
"parity": true,
|
|
38
|
-
"baseline_us":
|
|
39
|
-
"candidate_us":
|
|
40
|
-
"ratio":
|
|
38
|
+
"baseline_us": 64.4,
|
|
39
|
+
"candidate_us": 19.0,
|
|
40
|
+
"ratio": 3.387,
|
|
41
41
|
"baseline_allocs": 13,
|
|
42
42
|
"candidate_allocs": 8
|
|
43
43
|
},
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"row": "xpath //field (each)",
|
|
46
46
|
"kind": "structural",
|
|
47
47
|
"parity": false,
|
|
48
|
-
"baseline_us":
|
|
49
|
-
"candidate_us":
|
|
50
|
-
"ratio": 0.
|
|
48
|
+
"baseline_us": 256.4,
|
|
49
|
+
"candidate_us": 744.4,
|
|
50
|
+
"ratio": 0.344,
|
|
51
51
|
"baseline_allocs": 1213,
|
|
52
52
|
"candidate_allocs": 10
|
|
53
53
|
},
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"row": "attr pairs listing",
|
|
56
56
|
"kind": "parity",
|
|
57
57
|
"parity": true,
|
|
58
|
-
"baseline_us":
|
|
59
|
-
"candidate_us":
|
|
60
|
-
"ratio":
|
|
58
|
+
"baseline_us": 503.6,
|
|
59
|
+
"candidate_us": 59.4,
|
|
60
|
+
"ratio": 8.484,
|
|
61
61
|
"baseline_allocs": 3300,
|
|
62
62
|
"candidate_allocs": 0
|
|
63
63
|
},
|
|
@@ -65,19 +65,19 @@
|
|
|
65
65
|
"row": "build add_element",
|
|
66
66
|
"kind": "structural",
|
|
67
67
|
"parity": false,
|
|
68
|
-
"baseline_us":
|
|
69
|
-
"candidate_us":
|
|
70
|
-
"ratio": 0.
|
|
68
|
+
"baseline_us": 5607.1,
|
|
69
|
+
"candidate_us": 7620.4,
|
|
70
|
+
"ratio": 0.736,
|
|
71
71
|
"baseline_allocs": 20358,
|
|
72
|
-
"candidate_allocs":
|
|
72
|
+
"candidate_allocs": 10163
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"row": "walk+read (warm)",
|
|
76
76
|
"kind": "structural",
|
|
77
77
|
"parity": false,
|
|
78
|
-
"baseline_us":
|
|
79
|
-
"candidate_us":
|
|
80
|
-
"ratio": 0.
|
|
78
|
+
"baseline_us": 1631.1,
|
|
79
|
+
"candidate_us": 3005.5,
|
|
80
|
+
"ratio": 0.543,
|
|
81
81
|
"baseline_allocs": 4503,
|
|
82
82
|
"candidate_allocs": 2
|
|
83
83
|
},
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
"row": "walk+read (cold)",
|
|
86
86
|
"kind": "structural",
|
|
87
87
|
"parity": false,
|
|
88
|
-
"baseline_us":
|
|
89
|
-
"candidate_us":
|
|
90
|
-
"ratio": 0.
|
|
88
|
+
"baseline_us": 3094.4,
|
|
89
|
+
"candidate_us": 8308.1,
|
|
90
|
+
"ratio": 0.372,
|
|
91
91
|
"baseline_allocs": 6011,
|
|
92
92
|
"candidate_allocs": 10232
|
|
93
93
|
},
|
|
@@ -95,9 +95,9 @@
|
|
|
95
95
|
"row": "children recursion (cold)",
|
|
96
96
|
"kind": "structural",
|
|
97
97
|
"parity": false,
|
|
98
|
-
"baseline_us":
|
|
99
|
-
"candidate_us":
|
|
100
|
-
"ratio": 0.
|
|
98
|
+
"baseline_us": 3414.5,
|
|
99
|
+
"candidate_us": 7574.2,
|
|
100
|
+
"ratio": 0.451,
|
|
101
101
|
"baseline_allocs": 5711,
|
|
102
102
|
"candidate_allocs": 8732
|
|
103
103
|
},
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"row": "plan records (typed)",
|
|
106
106
|
"kind": "data",
|
|
107
107
|
"parity": false,
|
|
108
|
-
"baseline_us":
|
|
109
|
-
"candidate_us":
|
|
110
|
-
"ratio": 1.
|
|
108
|
+
"baseline_us": 3056.3,
|
|
109
|
+
"candidate_us": 2700.7,
|
|
110
|
+
"ratio": 1.132,
|
|
111
111
|
"baseline_allocs": 8112,
|
|
112
112
|
"candidate_allocs": 13232
|
|
113
113
|
}
|
|
@@ -41,6 +41,7 @@ Generated by `bundle exec rake docs:coverage` from the coverage matrix and the d
|
|
|
41
41
|
| materialization | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent
|
|
42
42
|
| validation | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent
|
|
43
43
|
| validation under-detections | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent
|
|
44
|
+
| validation version reporting | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent
|
|
44
45
|
| loud capability contracts | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent | equivalent
|
|
45
46
|
|===
|
|
46
47
|
|
|
@@ -14,21 +14,21 @@ the marked block below from a live measurement; see
|
|
|
14
14
|
// SCOREBOARD:BEGIN
|
|
15
15
|
Generated by `SCORE_PUBLISH=1 bundle exec ruby benchmark/scoreboard.rb`. Protocol: CPU-time medians of an interleaved A/B run (raw nokogiri vs moxml on leptris); the ratio is baseline / candidate, so > 1.0 means the facade is faster. Warmups discarded, GC between samples.
|
|
16
16
|
|
|
17
|
-
Measurement: Ruby 3.3.13, leptris 1.9.381.0, nokogiri 1.19.4, 2026-10-
|
|
17
|
+
Measurement: Ruby 3.3.13, leptris 1.9.381.0, nokogiri 1.19.4, 2026-10-24T05:26:53Z, 1-minute load 1.74, 9 repetitions.
|
|
18
18
|
|
|
19
19
|
[cols="2,1,1,1",options="header"]
|
|
20
20
|
|===
|
|
21
21
|
| Row | Ratio | Kind | Status
|
|
22
|
-
| `parse` |
|
|
23
|
-
| `serialize` | 1.
|
|
24
|
-
| `xpath //field (count)` |
|
|
25
|
-
| `xpath //field (each)` | 0.
|
|
26
|
-
| `attr pairs listing` |
|
|
27
|
-
| `build add_element` | 0.
|
|
28
|
-
| `walk+read (warm)` | 0.
|
|
29
|
-
| `walk+read (cold)` | 0.
|
|
30
|
-
| `children recursion (cold)` | 0.
|
|
31
|
-
| `plan records (typed)` | 1.
|
|
22
|
+
| `parse` | 4.61x | parity | met
|
|
23
|
+
| `serialize` | 1.65x | parity | met
|
|
24
|
+
| `xpath //field (count)` | 3.39x | parity | met
|
|
25
|
+
| `xpath //field (each)` | 0.34x | structural | tracked (below parity)
|
|
26
|
+
| `attr pairs listing` | 8.48x | parity | met
|
|
27
|
+
| `build add_element` | 0.74x | structural | tracked (below parity)
|
|
28
|
+
| `walk+read (warm)` | 0.54x | structural | tracked (below parity)
|
|
29
|
+
| `walk+read (cold)` | 0.37x | structural | tracked (below parity), paused: leptris-ruby#527 (1.9.380 crossing regression)
|
|
30
|
+
| `children recursion (cold)` | 0.45x | structural | tracked (below parity), paused: leptris-ruby#527 (1.9.380 crossing regression)
|
|
31
|
+
| `plan records (typed)` | 1.13x | data | informational
|
|
32
32
|
|===
|
|
33
33
|
|
|
34
34
|
*Parity rows* must stay at or above the 0.95 measurement floor. *Structural rows* are measured and trend-locked (10% regression tolerance) while their gaps remain open: per-node wrapper minting costs (#312 endgame) and engine construction. *Data API* rows are informational.
|
|
@@ -255,7 +255,7 @@ semantics.
|
|
|
255
255
|
|
|
256
256
|
=== Validation
|
|
257
257
|
|
|
258
|
-
Pins:: `spec/moxml/xsd_spec.rb`, `spec/moxml/xsd_underdetection_spec.rb`
|
|
258
|
+
Pins:: `spec/moxml/xsd_spec.rb`, `spec/moxml/xsd_underdetection_spec.rb`, `spec/moxml/xsd_version_spec.rb`
|
|
259
259
|
|
|
260
260
|
XSD validation through engines that ship a validator (libxml2;
|
|
261
261
|
leptris tier-1: recognized roots, one-level content descent, lexical
|
|
@@ -263,9 +263,15 @@ checks). `validate` returns accumulated `ValidationError` entries —
|
|
|
263
263
|
message always; line/column/path where the engine reports them, nil
|
|
264
264
|
where it does not; `[]` means valid. Documents outside the engine's
|
|
265
265
|
scope validate laxly, except that strict `xs:any` /
|
|
266
|
-
`xs:anyAttribute` wildcard matches
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
`xs:anyAttribute` wildcard matches, `xsi:nil`, and the XSD 1.1
|
|
267
|
+
negative wildcards (`notQName` / `notNamespace` — an item excluded
|
|
268
|
+
by them has no particle and the content model cannot accept it) are
|
|
269
|
+
re-checked from the schema source on every adapter — the re-check
|
|
270
|
+
can only under-report, never false-report. A compiled schema reports the
|
|
271
|
+
version its source declares: root `vc:minVersion="1.1"` scopes the
|
|
272
|
+
document to 1.1, anything else is 1.0 (consumers scope 1.0-only
|
|
273
|
+
checks with it; applying 1.1 semantics is the engine-side program).
|
|
274
|
+
Unsupported adapters raise
|
|
269
275
|
`Moxml::NotImplementedError` at compile; `Moxml::XSD.supported?`
|
|
270
276
|
answers without raising. RELAX NG rides engines with an RNG face
|
|
271
277
|
(the same shapes).
|
data/lib/moxml/version.rb
CHANGED
|
@@ -8,7 +8,11 @@ module Moxml
|
|
|
8
8
|
#
|
|
9
9
|
# * a strict `xs:any` wildcard with an undeclared element,
|
|
10
10
|
# * a strict `xs:anyAttribute` with an undeclared attribute,
|
|
11
|
-
# * `xsi:nil` on an element whose declaration is not nillable
|
|
11
|
+
# * `xsi:nil` on an element whose declaration is not nillable,
|
|
12
|
+
# * the XSD 1.1 negative wildcards — `notQName` /
|
|
13
|
+
# `notNamespace` on `xs:any` / `xs:anyAttribute` (#460):
|
|
14
|
+
# engines that compile them without enforcing them absorb
|
|
15
|
+
# excluded items the content model cannot accept.
|
|
12
16
|
#
|
|
13
17
|
# The rules run from an index of the schema SOURCE (this gem's
|
|
14
18
|
# own XML machinery — no engine involvement) and merge with the
|
|
@@ -20,22 +24,18 @@ module Moxml
|
|
|
20
24
|
module SchemaRules
|
|
21
25
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
|
22
26
|
|
|
23
|
-
# A wildcard's namespace
|
|
24
|
-
# Structures §3.10.2 (##any default;
|
|
25
|
-
# strict
|
|
26
|
-
#
|
|
27
|
+
# A wildcard's namespace constraint and disallowed names, per
|
|
28
|
+
# XML Schema Structures §3.10.1–3.10.2 (##any default;
|
|
29
|
+
# processContents default strict; notNamespace / notQName are
|
|
30
|
+
# XSD 1.1). :tns / :local are resolved against the indexed
|
|
31
|
+
# schema at check time; notQName QNames are resolved against
|
|
32
|
+
# the schema element's in-scope bindings at index time.
|
|
27
33
|
class Wildcard
|
|
28
|
-
def initialize(value, process
|
|
34
|
+
def initialize(value, process, not_namespace: nil, not_qname: nil,
|
|
35
|
+
element: nil)
|
|
29
36
|
@process = (process || "strict").to_sym
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@mode = :any
|
|
33
|
-
when "##other"
|
|
34
|
-
@mode = :other
|
|
35
|
-
else
|
|
36
|
-
@mode = :list
|
|
37
|
-
@namespaces = value.split(/\s+/).reject(&:empty?).freeze
|
|
38
|
-
end
|
|
37
|
+
@mode, @namespaces = namespace_constraint(value, not_namespace)
|
|
38
|
+
@disallowed = disallowed_names(not_qname, element).freeze
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def strict?
|
|
@@ -47,13 +47,89 @@ module Moxml
|
|
|
47
47
|
when :any then true
|
|
48
48
|
when :other
|
|
49
49
|
namespace != :local && namespace != :tns
|
|
50
|
+
when :not
|
|
51
|
+
@namespaces.none? { |entry| entry_covers?(entry, namespace) }
|
|
50
52
|
else
|
|
51
53
|
@namespaces.any? { |entry| entry_covers?(entry, namespace) }
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# Why the wildcard refuses an item outright (Structures
|
|
58
|
+
# §3.10.4.1 Item Valid): a refusal is a match-level failure,
|
|
59
|
+
# so it applies at every processContents — the content model
|
|
60
|
+
# has no particle for the item and cannot accept it. 1.0
|
|
61
|
+
# namespace lists stay the engine's enforcement and never
|
|
62
|
+
# name an exclusion here.
|
|
63
|
+
def negative_exclusion(qname, namespace, defined:,
|
|
64
|
+
sibling_names: [])
|
|
65
|
+
@disallowed.each do |entry|
|
|
66
|
+
case entry
|
|
67
|
+
when :defined
|
|
68
|
+
return :not_qname if defined
|
|
69
|
+
when :sibling
|
|
70
|
+
return :not_qname if sibling_names.include?(qname)
|
|
71
|
+
else
|
|
72
|
+
return :not_qname if entry == qname
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
return :not_namespace if @mode == :not &&
|
|
76
|
+
@namespaces.any? { |entry| entry_covers?(entry, namespace) }
|
|
77
|
+
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
55
81
|
private
|
|
56
82
|
|
|
83
|
+
def namespace_constraint(value, not_namespace)
|
|
84
|
+
if not_namespace
|
|
85
|
+
[:not, not_namespace.split(/\s+/).reject(&:empty?)]
|
|
86
|
+
elsif value.nil? || value == "##any" || value == ""
|
|
87
|
+
[:any, []]
|
|
88
|
+
elsif value == "##other"
|
|
89
|
+
[:other, []]
|
|
90
|
+
else
|
|
91
|
+
[:list, value.split(/\s+/).reject(&:empty?)]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# notQName entries (§3.10.2): QName | ##defined |
|
|
96
|
+
# ##definedSibling (xs:any; ##defined only on
|
|
97
|
+
# xs:anyAttribute). Prefixed QNames resolve against the
|
|
98
|
+
# wildcard element's in-scope bindings; unprefixed QNames
|
|
99
|
+
# carry no namespace — XSD QName values take no default
|
|
100
|
+
# namespace. Unresolvable prefixes are dropped: the index
|
|
101
|
+
# under-reports, never false-reports.
|
|
102
|
+
def disallowed_names(not_qname, element)
|
|
103
|
+
not_qname.to_s.split(/\s+/).reject(&:empty?).filter_map do |token|
|
|
104
|
+
case token
|
|
105
|
+
when "##defined" then :defined
|
|
106
|
+
when "##definedSibling" then :sibling
|
|
107
|
+
when /\A([^:]+):(.+)\z/
|
|
108
|
+
uri = prefix_uri(Regexp.last_match(1), element)
|
|
109
|
+
"{#{uri}}#{Regexp.last_match(2)}" unless uri.empty?
|
|
110
|
+
else
|
|
111
|
+
"{}#{token}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# node.namespaces is adapter-divergent (some engines return
|
|
117
|
+
# in-scope bindings, others own-node declarations only), so
|
|
118
|
+
# the in-scope map is walked out by hand — per-node
|
|
119
|
+
# definitions merged along the ancestor axis, deeper
|
|
120
|
+
# overriding shallower, identically on every adapter.
|
|
121
|
+
def prefix_uri(prefix, element)
|
|
122
|
+
bindings = {}
|
|
123
|
+
node = element
|
|
124
|
+
while node
|
|
125
|
+
node.namespaces.each do |namespace|
|
|
126
|
+
bindings[namespace.prefix] = namespace.uri
|
|
127
|
+
end
|
|
128
|
+
node = node.parent&.element? ? node.parent : nil
|
|
129
|
+
end
|
|
130
|
+
bindings[prefix].to_s
|
|
131
|
+
end
|
|
132
|
+
|
|
57
133
|
def entry_covers?(entry, namespace)
|
|
58
134
|
namespace == case entry
|
|
59
135
|
when "##targetNamespace" then :tns
|
|
@@ -67,7 +143,11 @@ module Moxml
|
|
|
67
143
|
:declared_attributes, :any_attribute)
|
|
68
144
|
SchemaIndex = Struct.new(:target_namespace, :element_default,
|
|
69
145
|
:attribute_default, :elements,
|
|
70
|
-
:attributes, :contents)
|
|
146
|
+
:attributes, :contents, :version)
|
|
147
|
+
|
|
148
|
+
# The XSD 1.1 version-tagging namespace (Structures §C.3):
|
|
149
|
+
# vc:minVersion / vc:maxVersion gate 1.1 constructs.
|
|
150
|
+
VC_NS = "http://www.w3.org/2001/XMLSchema-versioning"
|
|
71
151
|
|
|
72
152
|
class << self
|
|
73
153
|
# Index one schema document. Returns nil when the source
|
|
@@ -81,7 +161,7 @@ module Moxml
|
|
|
81
161
|
root["targetNamespace"].to_s,
|
|
82
162
|
root["elementFormDefault"] == "qualified",
|
|
83
163
|
root["attributeFormDefault"] == "qualified",
|
|
84
|
-
{}, {}, {}
|
|
164
|
+
{}, {}, {}, schema_version(root)
|
|
85
165
|
)
|
|
86
166
|
child_elements(root).each do |decl|
|
|
87
167
|
case decl.name
|
|
@@ -94,6 +174,20 @@ module Moxml
|
|
|
94
174
|
nil
|
|
95
175
|
end
|
|
96
176
|
|
|
177
|
+
# The effective schema version declared at the root
|
|
178
|
+
# (#460 item 2): vc:minVersion="1.1" on <xs:schema> scopes
|
|
179
|
+
# the document to 1.1 semantics; anything else reports
|
|
180
|
+
# 1.0. Conditional (subtree-level) vc: evaluation is the
|
|
181
|
+
# engine's conditional-assignment work — the facade reports
|
|
182
|
+
# the root declaration only.
|
|
183
|
+
def schema_version(root)
|
|
184
|
+
declared = root.attributes.find do |attribute|
|
|
185
|
+
attribute.namespace&.uri == VC_NS &&
|
|
186
|
+
attribute.name == "minVersion"
|
|
187
|
+
end&.value
|
|
188
|
+
declared.to_s.start_with?("1.1") ? "1.1" : "1.0"
|
|
189
|
+
end
|
|
190
|
+
|
|
97
191
|
# Post-validation findings for one instance document.
|
|
98
192
|
def findings(doc, index)
|
|
99
193
|
return [] unless index
|
|
@@ -175,12 +269,9 @@ module Moxml
|
|
|
175
269
|
add_particle(child, index, model,
|
|
176
270
|
model.declared_attributes)
|
|
177
271
|
when "any"
|
|
178
|
-
model.any ||=
|
|
179
|
-
child["processContents"])
|
|
272
|
+
model.any ||= wildcard_from(child)
|
|
180
273
|
when "anyAttribute"
|
|
181
|
-
model.any_attribute ||=
|
|
182
|
-
Wildcard.new(child["namespace"],
|
|
183
|
-
child["processContents"])
|
|
274
|
+
model.any_attribute ||= wildcard_from(child)
|
|
184
275
|
else
|
|
185
276
|
collect_model(child, index, model)
|
|
186
277
|
end
|
|
@@ -216,19 +307,25 @@ module Moxml
|
|
|
216
307
|
end
|
|
217
308
|
end
|
|
218
309
|
|
|
310
|
+
def wildcard_from(node)
|
|
311
|
+
Wildcard.new(
|
|
312
|
+
node["namespace"], node["processContents"],
|
|
313
|
+
not_namespace: node["notNamespace"],
|
|
314
|
+
not_qname: node["notQName"],
|
|
315
|
+
element: node
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
219
319
|
def child_findings(child, model, index)
|
|
220
320
|
qname = qname(child)
|
|
221
321
|
findings = []
|
|
222
322
|
if model.declared_children.include?(qname)
|
|
223
323
|
declaration = index.elements[qname]
|
|
224
324
|
findings << nil_finding(child, declaration) if declaration
|
|
225
|
-
elsif
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"wildcard but is not declared",
|
|
230
|
-
path: path_of(child),
|
|
231
|
-
)
|
|
325
|
+
elsif model.any &&
|
|
326
|
+
(finding = wildcard_child_finding(child, model, index,
|
|
327
|
+
qname))
|
|
328
|
+
findings << finding
|
|
232
329
|
end
|
|
233
330
|
findings.concat(
|
|
234
331
|
attribute_findings(child, index.contents[qname], index),
|
|
@@ -236,13 +333,34 @@ module Moxml
|
|
|
236
333
|
findings
|
|
237
334
|
end
|
|
238
335
|
|
|
239
|
-
def
|
|
336
|
+
def wildcard_child_finding(child, model, index, qname)
|
|
240
337
|
wildcard = model.any
|
|
241
|
-
return false unless wildcard&.strict?
|
|
242
|
-
return false if index.elements.key?(qname)
|
|
243
|
-
|
|
244
338
|
namespace = resolve_namespace(qname, index)
|
|
245
|
-
|
|
339
|
+
exclusion = wildcard.negative_exclusion(
|
|
340
|
+
qname, namespace,
|
|
341
|
+
defined: index.elements.key?(qname),
|
|
342
|
+
sibling_names: model.declared_children
|
|
343
|
+
)
|
|
344
|
+
return excluded_child_finding(child, qname, exclusion) if exclusion
|
|
345
|
+
|
|
346
|
+
return unless wildcard.covers?(namespace) && wildcard.strict?
|
|
347
|
+
return if index.elements.key?(qname)
|
|
348
|
+
|
|
349
|
+
Moxml::XSD::ValidationError.new(
|
|
350
|
+
message: "element <#{local(qname)}> matches a strict " \
|
|
351
|
+
"wildcard but is not declared",
|
|
352
|
+
path: path_of(child),
|
|
353
|
+
)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def excluded_child_finding(child, qname, exclusion)
|
|
357
|
+
reason = exclusion == :not_namespace ? "notNamespace" : "notQName"
|
|
358
|
+
Moxml::XSD::ValidationError.new(
|
|
359
|
+
message: "element <#{local(qname)}> is disallowed by a " \
|
|
360
|
+
"wildcard (#{reason}) and is not declared in the " \
|
|
361
|
+
"content model",
|
|
362
|
+
path: path_of(child),
|
|
363
|
+
)
|
|
246
364
|
end
|
|
247
365
|
|
|
248
366
|
def attribute_findings(element, model, index)
|
|
@@ -253,14 +371,19 @@ module Moxml
|
|
|
253
371
|
next if attribute.namespace&.uri == XSI_NS
|
|
254
372
|
|
|
255
373
|
qname = attribute_qname(attribute)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
374
|
+
next if model.declared_attributes.include?(qname)
|
|
375
|
+
next unless any_attribute
|
|
376
|
+
|
|
377
|
+
namespace = resolve_namespace(qname, index)
|
|
378
|
+
exclusion = any_attribute.negative_exclusion(
|
|
379
|
+
qname, namespace,
|
|
380
|
+
defined: index.attributes.key?(qname)
|
|
263
381
|
)
|
|
382
|
+
next excluded_attribute_finding(element, qname, exclusion) if exclusion
|
|
383
|
+
|
|
384
|
+
next unless any_attribute.covers?(namespace)
|
|
385
|
+
next unless any_attribute.strict?
|
|
386
|
+
next if index.attributes.key?(qname)
|
|
264
387
|
|
|
265
388
|
Moxml::XSD::ValidationError.new(
|
|
266
389
|
message: "attribute #{local(qname)} matches a strict " \
|
|
@@ -270,6 +393,16 @@ module Moxml
|
|
|
270
393
|
end
|
|
271
394
|
end
|
|
272
395
|
|
|
396
|
+
def excluded_attribute_finding(element, qname, exclusion)
|
|
397
|
+
reason = exclusion == :not_namespace ? "notNamespace" : "notQName"
|
|
398
|
+
Moxml::XSD::ValidationError.new(
|
|
399
|
+
message: "attribute #{local(qname)} is disallowed by a " \
|
|
400
|
+
"wildcard (#{reason}) and is not declared in the " \
|
|
401
|
+
"content model",
|
|
402
|
+
path: path_of(element),
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
|
|
273
406
|
def nil_finding(element, declaration)
|
|
274
407
|
return if declaration[:nillable]
|
|
275
408
|
|
data/lib/moxml/xsd.rb
CHANGED
|
@@ -175,6 +175,15 @@ module Moxml
|
|
|
175
175
|
@context.config.adapter.xsd_declarations(@handle)
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
+
# The effective schema version the source declares (#460 item
|
|
179
|
+
# 2): "1.1" when the root carries vc:minVersion="1.1", else
|
|
180
|
+
# "1.0". Consumers (lutaml-xsd) scope 1.0-only checks with
|
|
181
|
+
# it; the engine's application of 1.1 semantics is the
|
|
182
|
+
# engine-side program.
|
|
183
|
+
def version
|
|
184
|
+
@rules_index ? @rules_index.version : "1.0"
|
|
185
|
+
end
|
|
186
|
+
|
|
178
187
|
# Deterministic drop (#362.3): true when the adapter freed
|
|
179
188
|
# the engine-side schema NOW; false when the adapter can only
|
|
180
189
|
# wait for GC (documented per adapter). After a true release
|
|
@@ -4,11 +4,11 @@ require "spec_helper"
|
|
|
4
4
|
|
|
5
5
|
# Processing model §Validation (normative): pinned here.
|
|
6
6
|
# #452: tier-1 engines can accept, inside their recognized scope,
|
|
7
|
-
# instances that violate strict wildcards or xsi:nil.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# corpus shapes.
|
|
7
|
+
# instances that violate strict wildcards or xsi:nil. #460: engines
|
|
8
|
+
# may compile the 1.1 negative wildcards without enforcing them.
|
|
9
|
+
# The Ruby-side SchemaRules post-validation closes those gaps from
|
|
10
|
+
# the schema source; findings merge with the engine's (duplicates
|
|
11
|
+
# dropped), so every adapter answers invalid for the corpus shapes.
|
|
12
12
|
RSpec.describe "Moxml::XSD tier-1 under-detections (#452)" do
|
|
13
13
|
%i[nokogiri leptris].each do |adapter_name|
|
|
14
14
|
describe "on #{adapter_name}" do
|
|
@@ -152,6 +152,172 @@ RSpec.describe "Moxml::XSD tier-1 under-detections (#452)" do
|
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
describe "negative wildcards (XSD 1.1, #460)" do
|
|
156
|
+
before do
|
|
157
|
+
if adapter_name == :nokogiri
|
|
158
|
+
skip "#{adapter_name} requires an XSD 1.1 wildcard-capable " \
|
|
159
|
+
"engine"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe "xs:any notQName naming an excluded element" do
|
|
164
|
+
let(:schema) do
|
|
165
|
+
ctx.xsd(<<~XSD)
|
|
166
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
167
|
+
xmlns:w="http://example.com/w"
|
|
168
|
+
targetNamespace="http://example.com/w"
|
|
169
|
+
elementFormDefault="qualified">
|
|
170
|
+
<xs:element name="container">
|
|
171
|
+
<xs:complexType>
|
|
172
|
+
<xs:sequence>
|
|
173
|
+
<xs:any processContents="strict"
|
|
174
|
+
notQName="w:banned" minOccurs="0"
|
|
175
|
+
maxOccurs="unbounded"/>
|
|
176
|
+
</xs:sequence>
|
|
177
|
+
</xs:complexType>
|
|
178
|
+
</xs:element>
|
|
179
|
+
<xs:element name="banned" type="xs:string"/>
|
|
180
|
+
<xs:element name="known" type="xs:string"/>
|
|
181
|
+
</xs:schema>
|
|
182
|
+
XSD
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "rejects the excluded element although it is declared" do
|
|
186
|
+
doc = ctx.parse(
|
|
187
|
+
'<container xmlns="http://example.com/w"><banned>x</banned></container>',
|
|
188
|
+
)
|
|
189
|
+
expect(schema.valid?(doc)).to be(false)
|
|
190
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
191
|
+
/disallowed by a wildcard \(notQName\)/,
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "accepts a wildcard-matched declared element" do
|
|
196
|
+
doc = ctx.parse(
|
|
197
|
+
'<container xmlns="http://example.com/w"><known>x</known></container>',
|
|
198
|
+
)
|
|
199
|
+
expect(schema.valid?(doc)).to be(true)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "xs:any notQName=##defined" do
|
|
204
|
+
let(:schema) do
|
|
205
|
+
ctx.xsd(<<~XSD)
|
|
206
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
207
|
+
targetNamespace="http://example.com/w"
|
|
208
|
+
elementFormDefault="qualified">
|
|
209
|
+
<xs:element name="container">
|
|
210
|
+
<xs:complexType>
|
|
211
|
+
<xs:sequence>
|
|
212
|
+
<xs:any processContents="lax"
|
|
213
|
+
notQName="##defined" minOccurs="0"
|
|
214
|
+
maxOccurs="unbounded"/>
|
|
215
|
+
</xs:sequence>
|
|
216
|
+
</xs:complexType>
|
|
217
|
+
</xs:element>
|
|
218
|
+
<xs:element name="known" type="xs:string"/>
|
|
219
|
+
</xs:schema>
|
|
220
|
+
XSD
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "rejects a declared element the ##defined wildcard excludes" do
|
|
224
|
+
doc = ctx.parse(
|
|
225
|
+
'<container xmlns="http://example.com/w"><known>x</known></container>',
|
|
226
|
+
)
|
|
227
|
+
expect(schema.valid?(doc)).to be(false)
|
|
228
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
229
|
+
/disallowed by a wildcard \(notQName\)/,
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "absorbs an undeclared element laxly" do
|
|
234
|
+
doc = ctx.parse(
|
|
235
|
+
'<container xmlns="http://example.com/w"><unknown>x</unknown></container>',
|
|
236
|
+
)
|
|
237
|
+
expect(schema.valid?(doc)).to be(true)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
describe "xs:any notNamespace" do
|
|
242
|
+
let(:schema) do
|
|
243
|
+
ctx.xsd(<<~XSD)
|
|
244
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
245
|
+
targetNamespace="http://example.com/w"
|
|
246
|
+
elementFormDefault="qualified">
|
|
247
|
+
<xs:element name="container">
|
|
248
|
+
<xs:complexType>
|
|
249
|
+
<xs:sequence>
|
|
250
|
+
<xs:any processContents="lax"
|
|
251
|
+
notNamespace="##targetNamespace"
|
|
252
|
+
minOccurs="0" maxOccurs="unbounded"/>
|
|
253
|
+
</xs:sequence>
|
|
254
|
+
</xs:complexType>
|
|
255
|
+
</xs:element>
|
|
256
|
+
</xs:schema>
|
|
257
|
+
XSD
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "rejects an element in the disallowed namespace" do
|
|
261
|
+
doc = ctx.parse(
|
|
262
|
+
'<container xmlns="http://example.com/w"><stray/></container>',
|
|
263
|
+
)
|
|
264
|
+
expect(schema.valid?(doc)).to be(false)
|
|
265
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
266
|
+
/disallowed by a wildcard \(notNamespace\)/,
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "absorbs an element from an allowed namespace" do
|
|
271
|
+
doc = ctx.parse(
|
|
272
|
+
'<container xmlns="http://example.com/w">' \
|
|
273
|
+
'<stray xmlns="http://example.com/other"/></container>',
|
|
274
|
+
)
|
|
275
|
+
expect(schema.valid?(doc)).to be(true)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "xs:anyAttribute notQName=##defined" do
|
|
280
|
+
let(:schema) do
|
|
281
|
+
ctx.xsd(<<~XSD)
|
|
282
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
283
|
+
targetNamespace="http://example.com/w"
|
|
284
|
+
elementFormDefault="qualified"
|
|
285
|
+
attributeFormDefault="qualified">
|
|
286
|
+
<xs:element name="settings">
|
|
287
|
+
<xs:complexType>
|
|
288
|
+
<xs:complexContent>
|
|
289
|
+
<xs:restriction base="xs:anyType">
|
|
290
|
+
<xs:anyAttribute processContents="lax"
|
|
291
|
+
notQName="##defined"/>
|
|
292
|
+
</xs:restriction>
|
|
293
|
+
</xs:complexContent>
|
|
294
|
+
</xs:complexType>
|
|
295
|
+
</xs:element>
|
|
296
|
+
<xs:attribute name="marked" type="xs:string"/>
|
|
297
|
+
</xs:schema>
|
|
298
|
+
XSD
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
it "rejects a declared attribute the ##defined wildcard excludes" do
|
|
302
|
+
doc = ctx.parse(
|
|
303
|
+
'<settings xmlns="http://example.com/w" ' \
|
|
304
|
+
'xmlns:w="http://example.com/w" w:marked="x"/>',
|
|
305
|
+
)
|
|
306
|
+
expect(schema.valid?(doc)).to be(false)
|
|
307
|
+
expect(schema.validate(doc).map(&:message)).to include(
|
|
308
|
+
/attribute marked is disallowed by a wildcard \(notQName\)/,
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it "absorbs an undeclared attribute laxly" do
|
|
313
|
+
doc = ctx.parse(
|
|
314
|
+
'<settings xmlns="http://example.com/w" free="x"/>',
|
|
315
|
+
)
|
|
316
|
+
expect(schema.valid?(doc)).to be(true)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
155
321
|
describe "wrong-namespace root" do
|
|
156
322
|
let(:schema) do
|
|
157
323
|
ctx.xsd(<<~XSD)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Processing model §Validation (normative): pinned here.
|
|
6
|
+
|
|
7
|
+
# #460 item 2: a compiled schema reports the effective version its
|
|
8
|
+
# source declares — vc:minVersion="1.1" on the root scopes the
|
|
9
|
+
# document to 1.1 semantics; anything else reports 1.0. The
|
|
10
|
+
# detection is namespace-based (vc: is a convention, not a
|
|
11
|
+
# requirement) and reads the ROOT declaration; conditional
|
|
12
|
+
# (subtree-level) vc: evaluation is the engine's program.
|
|
13
|
+
RSpec.describe "Moxml::XSD::Schema#version (#460 item 2)" do
|
|
14
|
+
%i[nokogiri leptris].each do |adapter_name|
|
|
15
|
+
describe "on #{adapter_name}" do
|
|
16
|
+
let(:ctx) { Moxml.new(adapter_name) }
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
skip "#{adapter_name} requires an XSD-capable engine" unless
|
|
20
|
+
Moxml::XSD.supported?(ctx)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "reports 1.1 when the root declares vc:minVersion=1.1" do
|
|
24
|
+
schema = ctx.xsd(<<~XSD)
|
|
25
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
26
|
+
xmlns:vc="http://www.w3.org/2001/XMLSchema-versioning"
|
|
27
|
+
vc:minVersion="1.1">
|
|
28
|
+
<xs:element name="note" type="xs:string"/>
|
|
29
|
+
</xs:schema>
|
|
30
|
+
XSD
|
|
31
|
+
expect(schema.version).to eq("1.1")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "detects the versioning namespace, not the prefix" do
|
|
35
|
+
schema = ctx.xsd(<<~XSD)
|
|
36
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
37
|
+
xmlns:vcx="http://www.w3.org/2001/XMLSchema-versioning"
|
|
38
|
+
vcx:minVersion="1.1">
|
|
39
|
+
<xs:element name="note" type="xs:string"/>
|
|
40
|
+
</xs:schema>
|
|
41
|
+
XSD
|
|
42
|
+
expect(schema.version).to eq("1.1")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "reports 1.0 without a root version declaration" do
|
|
46
|
+
schema = ctx.xsd(<<~XSD)
|
|
47
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
48
|
+
<xs:element name="note" type="xs:string"/>
|
|
49
|
+
</xs:schema>
|
|
50
|
+
XSD
|
|
51
|
+
expect(schema.version).to eq("1.0")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "reports 1.0 for an explicit root vc:minVersion=1.0" do
|
|
55
|
+
schema = ctx.xsd(<<~XSD)
|
|
56
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
57
|
+
xmlns:vc="http://www.w3.org/2001/XMLSchema-versioning"
|
|
58
|
+
vc:minVersion="1.0">
|
|
59
|
+
<xs:element name="note" type="xs:string"/>
|
|
60
|
+
</xs:schema>
|
|
61
|
+
XSD
|
|
62
|
+
expect(schema.version).to eq("1.0")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "reports 1.0 when only a construct carries vc:minVersion" do
|
|
66
|
+
schema = ctx.xsd(<<~XSD)
|
|
67
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
68
|
+
xmlns:vc="http://www.w3.org/2001/XMLSchema-versioning">
|
|
69
|
+
<xs:element name="note">
|
|
70
|
+
<xs:complexType>
|
|
71
|
+
<xs:sequence vc:minVersion="1.1">
|
|
72
|
+
<xs:element name="a"/>
|
|
73
|
+
</xs:sequence>
|
|
74
|
+
</xs:complexType>
|
|
75
|
+
</xs:element>
|
|
76
|
+
</xs:schema>
|
|
77
|
+
XSD
|
|
78
|
+
expect(schema.version).to eq("1.0")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -85,6 +85,10 @@ module ModelCoverage
|
|
|
85
85
|
section: "Validation",
|
|
86
86
|
pin: "spec/moxml/xsd_underdetection_spec.rb",
|
|
87
87
|
},
|
|
88
|
+
"validation version reporting" => {
|
|
89
|
+
section: "Validation",
|
|
90
|
+
pin: "spec/moxml/xsd_version_spec.rb",
|
|
91
|
+
},
|
|
88
92
|
"loud capability contracts" => {
|
|
89
93
|
section: "Errors and loud degradation",
|
|
90
94
|
pin: "spec/consistency/loud_degradation_spec.rb",
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.163
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|
|
@@ -634,6 +634,7 @@ files:
|
|
|
634
634
|
- spec/moxml/xsd_release_spec.rb
|
|
635
635
|
- spec/moxml/xsd_spec.rb
|
|
636
636
|
- spec/moxml/xsd_underdetection_spec.rb
|
|
637
|
+
- spec/moxml/xsd_version_spec.rb
|
|
637
638
|
- spec/moxml/xslt/stylesheet_spec.rb
|
|
638
639
|
- spec/performance/README.md
|
|
639
640
|
- spec/performance/benchmark_spec.rb
|