moxml 0.5.160 → 0.5.162
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 +6 -2
- data/lib/moxml/adapter/leptris.rb +29 -47
- data/lib/moxml/element.rb +17 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd/schema_rules.rb +20 -2
- data/lib/moxml/xsd.rb +9 -0
- data/spec/moxml/adapter/columnar_invalidation_spec.rb +56 -0
- data/spec/moxml/xsd_version_spec.rb +82 -0
- data/spec/support/model_coverage.rb +4 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3ff9c9a02aad66036e51b20f9dd72fb17adb09b51dbe3950bb5a5174a2f2158
|
|
4
|
+
data.tar.gz: b4924b020e3b4738e08197d918594df3269d5206fda86ab0f582b27e801a1dbf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dcb4225b3f662a656232b4e4bccdaefeb261a186d9c01d5369ee54ec28829678202e69349372842e470dcd21bed702f6f28c0e58dc9e45b6c262fffab1ed937a
|
|
7
|
+
data.tar.gz: 22fa41a31b5a8541e2c3f5fa2c8eb088975f173902ff8c046e325ec55c28246e3c459df5e49d004fb2e89ea1d1d8f9adb841d71cae8e4fe8bfaea14cd25c5d7c
|
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
|
|
@@ -265,7 +265,11 @@ where it does not; `[]` means valid. Documents outside the engine's
|
|
|
265
265
|
scope validate laxly, except that strict `xs:any` /
|
|
266
266
|
`xs:anyAttribute` wildcard matches and `xsi:nil` are re-checked from
|
|
267
267
|
the schema source on every adapter — the re-check can only
|
|
268
|
-
under-report, never false-report.
|
|
268
|
+
under-report, never false-report. A compiled schema reports the
|
|
269
|
+
version its source declares: root `vc:minVersion="1.1"` scopes the
|
|
270
|
+
document to 1.1, anything else is 1.0 (consumers scope 1.0-only
|
|
271
|
+
checks with it; applying 1.1 semantics is the engine-side program).
|
|
272
|
+
Unsupported adapters raise
|
|
269
273
|
`Moxml::NotImplementedError` at compile; `Moxml::XSD.supported?`
|
|
270
274
|
answers without raising. RELAX NG rides engines with an RNG face
|
|
271
275
|
(the same shapes).
|
|
@@ -83,6 +83,15 @@ module Moxml
|
|
|
83
83
|
::Leptris::XML::Native.respond_to?(:xpath_result_rows_bulk) &&
|
|
84
84
|
::Leptris::XML::Document.method_defined?(:xpath_result_rows_bulk)
|
|
85
85
|
|
|
86
|
+
# TODO.sota/04 (build row): ONE-crossing construction —
|
|
87
|
+
# create + attrs + attach (+ optional text) + version bump +
|
|
88
|
+
# consumer mint, all in C off the parent wrapper's struct
|
|
89
|
+
# (leptris-ruby perf/add-element-full). Probed on the class:
|
|
90
|
+
# older exts take the existing two-crossing path.
|
|
91
|
+
ADD_ELEMENT_FULL =
|
|
92
|
+
defined?(::Leptris::XML::NativeNode) &&
|
|
93
|
+
::Leptris::XML::NativeNode.method_defined?(:add_element_full)
|
|
94
|
+
|
|
86
95
|
# leptris-ruby#460: set-text by document+address, kind-dispatched
|
|
87
96
|
# in C — no binding twin mint per write. Face-probed, not
|
|
88
97
|
# version-gated (the floor admits bindings without it).
|
|
@@ -1334,8 +1343,6 @@ module Moxml
|
|
|
1334
1343
|
end
|
|
1335
1344
|
|
|
1336
1345
|
def set_root(doc, element)
|
|
1337
|
-
drop_columnar_children(doc)
|
|
1338
|
-
drop_columnar_children(element)
|
|
1339
1346
|
to_binding(doc).root = to_binding(element)
|
|
1340
1347
|
end
|
|
1341
1348
|
|
|
@@ -1735,7 +1742,6 @@ module Moxml
|
|
|
1735
1742
|
# re-indented). Raw-pointer walk over the root subtree — the
|
|
1736
1743
|
# same batch child-pointer pattern the materializer uses.
|
|
1737
1744
|
def strip_blank_text_nodes!(doc)
|
|
1738
|
-
drop_columnar_children(doc)
|
|
1739
1745
|
binding_ffi = ::Leptris::XML::FFI
|
|
1740
1746
|
root = binding_ffi.leptris_document_root(doc.c_ptr)
|
|
1741
1747
|
return unless root && !root.null?
|
|
@@ -2233,8 +2239,6 @@ module Moxml
|
|
|
2233
2239
|
# source. Answer the installed binding element; callers re-point
|
|
2234
2240
|
# wrappers at it.
|
|
2235
2241
|
def adopt_root(doc, element)
|
|
2236
|
-
drop_columnar_children(doc)
|
|
2237
|
-
drop_columnar_children(element)
|
|
2238
2242
|
return nil unless NATIVE_ROOT_ADOPT
|
|
2239
2243
|
|
|
2240
2244
|
binding_doc = to_binding(doc)
|
|
@@ -2345,7 +2349,10 @@ module Moxml
|
|
|
2345
2349
|
doc = doc_for(node)
|
|
2346
2350
|
return nil unless doc
|
|
2347
2351
|
|
|
2348
|
-
|
|
2352
|
+
pair = attachments.get(doc, COLUMNAR_CHILDREN)
|
|
2353
|
+
return nil unless pair.is_a?(Array) && pair[0] == doc.version
|
|
2354
|
+
|
|
2355
|
+
map = pair[1]
|
|
2349
2356
|
return nil unless map&.key?(node)
|
|
2350
2357
|
|
|
2351
2358
|
map[node].dup
|
|
@@ -2383,36 +2390,14 @@ module Moxml
|
|
|
2383
2390
|
end
|
|
2384
2391
|
mine << handle if depth == child_depth
|
|
2385
2392
|
end
|
|
2386
|
-
|
|
2393
|
+
# Memo-version law: the pair is stamped with the binding
|
|
2394
|
+
# Document#@version the map was primed under; every mutation
|
|
2395
|
+
# face advances that version in C, so a stale map misses the
|
|
2396
|
+
# hit check and re-primes — no write-side drop walk needed.
|
|
2397
|
+
attachments.set(doc, COLUMNAR_CHILDREN, [doc.version, map]) if map
|
|
2387
2398
|
mine
|
|
2388
2399
|
end
|
|
2389
2400
|
|
|
2390
|
-
# Structural seams drop the columnar map wholesale; the next
|
|
2391
|
-
# children read re-primes it (see COLUMNAR_WALK). Cheap no-op
|
|
2392
|
-
# when no map is live: one lock-free attachment probe.
|
|
2393
|
-
def drop_columnar_children(node)
|
|
2394
|
-
return unless COLUMNAR_WALK
|
|
2395
|
-
|
|
2396
|
-
doc =
|
|
2397
|
-
case node
|
|
2398
|
-
when ::Leptris::XML::NativeNode
|
|
2399
|
-
doc_for(node)
|
|
2400
|
-
when ::Leptris::XML::Document
|
|
2401
|
-
node
|
|
2402
|
-
else
|
|
2403
|
-
begin
|
|
2404
|
-
node.document
|
|
2405
|
-
rescue StandardError
|
|
2406
|
-
nil
|
|
2407
|
-
end
|
|
2408
|
-
end
|
|
2409
|
-
return if doc.nil?
|
|
2410
|
-
|
|
2411
|
-
return if attachments.get(doc, COLUMNAR_CHILDREN).nil?
|
|
2412
|
-
|
|
2413
|
-
attachments.delete(doc, COLUMNAR_CHILDREN)
|
|
2414
|
-
end
|
|
2415
|
-
|
|
2416
2401
|
def native_keep_entity_refs?(node)
|
|
2417
2402
|
return false unless NATIVE_READ_LAYER &&
|
|
2418
2403
|
node.is_a?(::Leptris::XML::NativeNode)
|
|
@@ -2625,8 +2610,18 @@ module Moxml
|
|
|
2625
2610
|
# loop is C->C dlsym). Requires parent binding + the face;
|
|
2626
2611
|
# callers fall back to create_element + set_attribute +
|
|
2627
2612
|
# add_child when absent.
|
|
2613
|
+
# TODO.sota/04: the ONE-crossing build face. Returns the
|
|
2614
|
+
# contract-carrying row, or nil when this binding cannot
|
|
2615
|
+
# serve it (caller takes the legacy path).
|
|
2616
|
+
def add_element_full(parent, name, attrs)
|
|
2617
|
+
return nil unless NATIVE_C_MINT && ADD_ELEMENT_FULL &&
|
|
2618
|
+
parent.is_a?(::Leptris::XML::NativeNode)
|
|
2619
|
+
|
|
2620
|
+
validate_element_name(name)
|
|
2621
|
+
parent.add_element_full(name.to_s, attrs.flatten, nil)
|
|
2622
|
+
end
|
|
2623
|
+
|
|
2628
2624
|
def create_element_with_attrs(parent, name, attrs)
|
|
2629
|
-
drop_columnar_children(parent)
|
|
2630
2625
|
validate_element_name(name)
|
|
2631
2626
|
# NativeNode parents need NO FFI twin (TODO.sota/04 bridge
|
|
2632
2627
|
# tax): the bridge existed only to obtain c_address +
|
|
@@ -2964,8 +2959,6 @@ module Moxml
|
|
|
2964
2959
|
# on the source, so a living source can never outlive the
|
|
2965
2960
|
# memory its reads still touch.
|
|
2966
2961
|
def absorb_source_document(dest, owner)
|
|
2967
|
-
drop_columnar_children(dest)
|
|
2968
|
-
drop_columnar_children(owner)
|
|
2969
2962
|
return nil unless NATIVE_DOC_ABSORB
|
|
2970
2963
|
return :direct if attachments.get(dest, :absorbed_sources)&.include?(owner)
|
|
2971
2964
|
|
|
@@ -3011,8 +3004,6 @@ module Moxml
|
|
|
3011
3004
|
end
|
|
3012
3005
|
|
|
3013
3006
|
def add_child(parent, child)
|
|
3014
|
-
drop_columnar_children(parent)
|
|
3015
|
-
drop_columnar_children(child)
|
|
3016
3007
|
result = add_child_impl(parent, child)
|
|
3017
3008
|
# A successful child insertion rebuilds the engine's
|
|
3018
3009
|
# document index; the upstream sibling-insert bug leaves it
|
|
@@ -3134,8 +3125,6 @@ module Moxml
|
|
|
3134
3125
|
end
|
|
3135
3126
|
|
|
3136
3127
|
def add_previous_sibling(node, new_node)
|
|
3137
|
-
drop_columnar_children(node)
|
|
3138
|
-
drop_columnar_children(new_node)
|
|
3139
3128
|
result = add_previous_sibling_impl(node, new_node)
|
|
3140
3129
|
mark_xpath_index_stale(node)
|
|
3141
3130
|
result
|
|
@@ -3188,8 +3177,6 @@ module Moxml
|
|
|
3188
3177
|
end
|
|
3189
3178
|
|
|
3190
3179
|
def add_next_sibling(node, new_node)
|
|
3191
|
-
drop_columnar_children(node)
|
|
3192
|
-
drop_columnar_children(new_node)
|
|
3193
3180
|
result = add_next_sibling_impl(node, new_node)
|
|
3194
3181
|
mark_xpath_index_stale(node)
|
|
3195
3182
|
result
|
|
@@ -3266,7 +3253,6 @@ module Moxml
|
|
|
3266
3253
|
end
|
|
3267
3254
|
|
|
3268
3255
|
def remove(node)
|
|
3269
|
-
drop_columnar_children(node)
|
|
3270
3256
|
node = to_binding(node) if NATIVE_READ_LAYER
|
|
3271
3257
|
case node
|
|
3272
3258
|
when CustomizedLeptris::Declaration
|
|
@@ -3298,8 +3284,6 @@ module Moxml
|
|
|
3298
3284
|
end
|
|
3299
3285
|
|
|
3300
3286
|
def replace(node, new_node)
|
|
3301
|
-
drop_columnar_children(node)
|
|
3302
|
-
drop_columnar_children(new_node)
|
|
3303
3287
|
if NATIVE_READ_LAYER
|
|
3304
3288
|
node = to_binding(node)
|
|
3305
3289
|
new_node = to_binding(new_node)
|
|
@@ -3358,7 +3342,6 @@ module Moxml
|
|
|
3358
3342
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.290.0")
|
|
3359
3343
|
|
|
3360
3344
|
def replace_children(node, new_children)
|
|
3361
|
-
drop_columnar_children(node)
|
|
3362
3345
|
# Cross-pool children: the binding's bulk face adopts per
|
|
3363
3346
|
# node and fails on mixed kinds (inner_xml= with
|
|
3364
3347
|
# text+elements died with Memory allocation failed —
|
|
@@ -3448,7 +3431,6 @@ module Moxml
|
|
|
3448
3431
|
::Leptris::XML::NativeNode.method_defined?(:set_text_native)
|
|
3449
3432
|
|
|
3450
3433
|
def set_text_content(node, content)
|
|
3451
|
-
drop_columnar_children(node)
|
|
3452
3434
|
if SET_TEXT_NATIVE && node.is_a?(::Leptris::XML::NativeNode)
|
|
3453
3435
|
begin
|
|
3454
3436
|
node.set_text_native(content.to_s)
|
data/lib/moxml/element.rb
CHANGED
|
@@ -139,10 +139,17 @@ module Moxml
|
|
|
139
139
|
# wrapper. Equivalent to create_element + attribute writes +
|
|
140
140
|
# add_child, minus two crossings and the intermediate churn.
|
|
141
141
|
def add_element(name, attrs = {})
|
|
142
|
+
# TODO.sota/04: one crossing when the binding serves it.
|
|
143
|
+
native_child = adapter.add_element_full(@native, name, attrs)
|
|
144
|
+
if native_child
|
|
145
|
+
attach_built_child(native_child)
|
|
146
|
+
return Moxml::Node.wrap_with(native_child, context, adapter)
|
|
147
|
+
end
|
|
142
148
|
native_child = adapter.create_element_with_attrs(
|
|
143
149
|
@native, name, attrs
|
|
144
150
|
)
|
|
145
151
|
if native_child
|
|
152
|
+
attach_built_child(native_child)
|
|
146
153
|
Moxml::Node.wrap_with(native_child, context, adapter)
|
|
147
154
|
else
|
|
148
155
|
fallback = Moxml::Node.wrap_with(
|
|
@@ -154,6 +161,16 @@ module Moxml
|
|
|
154
161
|
end
|
|
155
162
|
end
|
|
156
163
|
|
|
164
|
+
# The construction fast paths bypass add_child, so they owe the
|
|
165
|
+
# same invalidation contract: the ctx children clock retires every
|
|
166
|
+
# wrapper's generation-checked memo (the receiver's own included —
|
|
167
|
+
# the read re-checks the generation before serving).
|
|
168
|
+
def attach_built_child(native_child)
|
|
169
|
+
context.bump_children_generation
|
|
170
|
+
native_child.parent_node = self
|
|
171
|
+
native_child
|
|
172
|
+
end
|
|
173
|
+
|
|
157
174
|
def attributes
|
|
158
175
|
# Primed like Node.wrap: the adapter and type are known at
|
|
159
176
|
# mint, so the first name/value/attribute? access skips the
|
data/lib/moxml/version.rb
CHANGED
|
@@ -67,7 +67,11 @@ module Moxml
|
|
|
67
67
|
:declared_attributes, :any_attribute)
|
|
68
68
|
SchemaIndex = Struct.new(:target_namespace, :element_default,
|
|
69
69
|
:attribute_default, :elements,
|
|
70
|
-
:attributes, :contents)
|
|
70
|
+
:attributes, :contents, :version)
|
|
71
|
+
|
|
72
|
+
# The XSD 1.1 version-tagging namespace (Structures §C.3):
|
|
73
|
+
# vc:minVersion / vc:maxVersion gate 1.1 constructs.
|
|
74
|
+
VC_NS = "http://www.w3.org/2001/XMLSchema-versioning"
|
|
71
75
|
|
|
72
76
|
class << self
|
|
73
77
|
# Index one schema document. Returns nil when the source
|
|
@@ -81,7 +85,7 @@ module Moxml
|
|
|
81
85
|
root["targetNamespace"].to_s,
|
|
82
86
|
root["elementFormDefault"] == "qualified",
|
|
83
87
|
root["attributeFormDefault"] == "qualified",
|
|
84
|
-
{}, {}, {}
|
|
88
|
+
{}, {}, {}, schema_version(root)
|
|
85
89
|
)
|
|
86
90
|
child_elements(root).each do |decl|
|
|
87
91
|
case decl.name
|
|
@@ -94,6 +98,20 @@ module Moxml
|
|
|
94
98
|
nil
|
|
95
99
|
end
|
|
96
100
|
|
|
101
|
+
# The effective schema version declared at the root
|
|
102
|
+
# (#460 item 2): vc:minVersion="1.1" on <xs:schema> scopes
|
|
103
|
+
# the document to 1.1 semantics; anything else reports
|
|
104
|
+
# 1.0. Conditional (subtree-level) vc: evaluation is the
|
|
105
|
+
# engine's conditional-assignment work — the facade reports
|
|
106
|
+
# the root declaration only.
|
|
107
|
+
def schema_version(root)
|
|
108
|
+
declared = root.attributes.find do |attribute|
|
|
109
|
+
attribute.namespace&.uri == VC_NS &&
|
|
110
|
+
attribute.name == "minVersion"
|
|
111
|
+
end&.value
|
|
112
|
+
declared.to_s.start_with?("1.1") ? "1.1" : "1.0"
|
|
113
|
+
end
|
|
114
|
+
|
|
97
115
|
# Post-validation findings for one instance document.
|
|
98
116
|
def findings(doc, index)
|
|
99
117
|
return [] unless index
|
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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# The COLUMNAR_WALK children map must never serve rows older than a
|
|
6
|
+
# completed mutation, through EVERY write lane. This is the memo-version
|
|
7
|
+
# law (leptris-ruby#530) applied to the columnar map: the binding
|
|
8
|
+
# Document#@version advances on every mutation, so a map primed under
|
|
9
|
+
# an older version is stale by construction — independent of which
|
|
10
|
+
# adapter seam (or none) happens to drop it.
|
|
11
|
+
RSpec.describe "columnar children map invalidation" do
|
|
12
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
13
|
+
|
|
14
|
+
def walk_children(node)
|
|
15
|
+
node.children.map { |c| c.is_a?(Moxml::Element) ? c.name : c.text }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "serves fresh rows after content= replaces a child subtree" do
|
|
19
|
+
doc = ctx.parse("<r><a>t1</a><b>t2</b></r>")
|
|
20
|
+
a = doc.root.children.first
|
|
21
|
+
walk_children(a) # prime the columnar map over a's subtree
|
|
22
|
+
a.content = "replaced"
|
|
23
|
+
expect(walk_children(a)).to eq(["replaced"])
|
|
24
|
+
expect(walk_children(doc.root)).to eq(%w[a b])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "serves fresh rows after add_element" do
|
|
28
|
+
doc = ctx.parse("<r><a/></r>")
|
|
29
|
+
walk_children(doc.root)
|
|
30
|
+
doc.root.add_element("b", "k" => "v")
|
|
31
|
+
expect(walk_children(doc.root)).to eq(%w[a b])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "serves fresh rows after add_next_sibling" do
|
|
35
|
+
doc = ctx.parse("<r><a/></r>")
|
|
36
|
+
a = doc.root.children.first
|
|
37
|
+
walk_children(doc.root)
|
|
38
|
+
a.add_next_sibling("<c/>")
|
|
39
|
+
expect(walk_children(doc.root)).to eq(%w[a c])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "serves fresh rows after remove" do
|
|
43
|
+
doc = ctx.parse("<r><a/><b/></r>")
|
|
44
|
+
b = doc.root.children.last
|
|
45
|
+
walk_children(doc.root)
|
|
46
|
+
b.remove
|
|
47
|
+
expect(walk_children(doc.root)).to eq(%w[a])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "keeps the primed rows valid when nothing mutated" do
|
|
51
|
+
doc = ctx.parse("<r><a>t1</a><b>t2</b></r>")
|
|
52
|
+
expected = walk_children(doc.root)
|
|
53
|
+
expect(walk_children(doc.root)).to eq(expected)
|
|
54
|
+
expect(walk_children(doc.root)).to eq(expected)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -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,7 +1,7 @@
|
|
|
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.162
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -477,6 +477,7 @@ files:
|
|
|
477
477
|
- spec/moxml/adapter/.gitkeep
|
|
478
478
|
- spec/moxml/adapter/README.md
|
|
479
479
|
- spec/moxml/adapter/base_spec.rb
|
|
480
|
+
- spec/moxml/adapter/columnar_invalidation_spec.rb
|
|
480
481
|
- spec/moxml/adapter/entity_restoration_spec.rb
|
|
481
482
|
- spec/moxml/adapter/headed_ox_spec.rb
|
|
482
483
|
- spec/moxml/adapter/leptris_adoption_spec.rb
|
|
@@ -633,6 +634,7 @@ files:
|
|
|
633
634
|
- spec/moxml/xsd_release_spec.rb
|
|
634
635
|
- spec/moxml/xsd_spec.rb
|
|
635
636
|
- spec/moxml/xsd_underdetection_spec.rb
|
|
637
|
+
- spec/moxml/xsd_version_spec.rb
|
|
636
638
|
- spec/moxml/xslt/stylesheet_spec.rb
|
|
637
639
|
- spec/performance/README.md
|
|
638
640
|
- spec/performance/benchmark_spec.rb
|