leptris 1.9.380.2-arm-linux → 1.9.382.0-arm-linux
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/CHANGELOG.md +34 -0
- data/Rakefile +9 -2
- data/ext/leptris/native/native.c +140 -14
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +11 -1
- data/lib/leptris/xml/ffi.rb +18 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/leptris/xml/xsd.rb +54 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +70 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/TODO.SOTA.md +18 -2
- data/vendor-src/libleptris/TODO.max-perf/00-INDEX.md +4 -0
- data/vendor-src/libleptris/TODO.max-perf/15-c-face-vs-pugixml.md +87 -0
- data/vendor-src/libleptris/docs/MISSION-PLAN.md +1 -1
- data/vendor-src/libleptris/src/include/leptris.h +41 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.c +44 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.h +13 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +51 -0
- data/vendor-src/libleptris/src/leptris/dom/element.c +31 -39
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +30 -10
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +54 -13
- data/vendor-src/libleptris/src/leptris/leptris.c +12 -7
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +7 -0
- data/vendor-src/libleptris/src/leptris/memory/arena.c +3 -2
- data/vendor-src/libleptris/src/leptris/rng/rng_regex.c +411 -17
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_path.c +134 -4
- data/vendor-src/libleptris/src/leptris/xpath/vm.c +7 -7
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +105 -2
- data/vendor-src/libleptris/src/leptris/xsd/content_model.c +45 -4
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +129 -41
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +24 -0
- data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +42 -3
- data/vendor-src/libleptris/src/leptris/xslt/xslt_pattern.c +16 -9
- data/vendor-src/libleptris/test/dom/test_dom.cpp +42 -0
- data/vendor-src/libleptris/test/parser/test_simd_count.cpp +68 -0
- data/vendor-src/libleptris/test/rng/test_rng.cpp +69 -1
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +8 -3
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +310 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- 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: eeb080a56ba2f644ba7219077f866627006b2d5e8ea80405a86a17cff5853737
|
|
4
|
+
data.tar.gz: 8498da423561b35ddfabe900ae828df3ee9f9963c1498ac90cbb543f73c4c58e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 750842a780c631c64e41614fa9f6b1a812bec0cffe9965fa868967a0b0375b16d9b1466279a07b990a90bbb0f14ee307c3868e9f4dd1702c1436f1af54d3d3f7
|
|
7
|
+
data.tar.gz: 16cfef2895a0d2b8ccfb5324492183111dacf30e4db0261f310418a3f2f97dbd6c68b24828858573ecd5062ba2ec9e3fa0f83c5904e8b769234dde45f9b7e86b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
### Added — 1.9.382.0 (rides libleptris 1.9.382)
|
|
4
|
+
|
|
5
|
+
- **Schema#version** (moxml#460 item 2): the effective schema
|
|
6
|
+
version — "1.1" when the root declares `@version="1.1"` (1.1
|
|
7
|
+
semantics apply: `xs:alternative` CTA, `xs:assert`), else
|
|
8
|
+
"1.0". Engine face leptris_xsd_version (leptris/leptris#1849).
|
|
9
|
+
- **Native.name_equals** (leptris/leptris#1855 ask 2): engine-side
|
|
10
|
+
name comparison — no String materialization for name-comparing
|
|
11
|
+
consumers. Soft-gated; Qnil for non-element addresses.
|
|
12
|
+
- **Soft-attached faces** (leptris-ruby#548): version-gated symbols
|
|
13
|
+
no longer break the load on older engines; the symbol audit
|
|
14
|
+
exempts them until the pin reaches their release.
|
|
15
|
+
|
|
3
16
|
### Fixed — engine ride
|
|
4
17
|
|
|
18
|
+
- **Ride libleptris 1.9.382**: XSD structured error rows
|
|
19
|
+
(leptris/leptris#1843), negative wildcards (#1850), block +
|
|
20
|
+
category escapes in the pattern regex (#1853/#1856/#1857), the
|
|
21
|
+
parse-perf campaign work (#1844/#1847/#1848/#1851), and the
|
|
22
|
+
CAS-only retain spinlock.
|
|
23
|
+
- **Ride libleptris 1.9.380**: the xsi:type conformance fix
|
|
24
|
+
(leptris/leptris#1832, lutaml/moxml#433) — an element carrying
|
|
25
|
+
`xsi:type` is assessed against the NAMED type (XSD 1.0
|
|
26
|
+
§2.6.2/§3.3.4), so valid xsi:type-derived instances validate
|
|
27
|
+
instead of being rejected against the declared type's content
|
|
28
|
+
model. Released 1.9.380.0 with the pin still at 1.9.379 (the
|
|
29
|
+
vendored dylib missed the fix); this pin bump is the real ride.
|
|
30
|
+
|
|
5
31
|
- **Ride libleptris 1.9.380**: the xsi:type conformance fix
|
|
6
32
|
(leptris/leptris#1832, lutaml/moxml#433) — an element carrying
|
|
7
33
|
`xsi:type` is assessed against the NAMED type (XSD 1.0
|
|
@@ -17,6 +43,14 @@
|
|
|
17
43
|
parameters and marks all-native results; 1.9.380.1 shipped before
|
|
18
44
|
the PR merged, so the face rides in 1.9.380.2.
|
|
19
45
|
|
|
46
|
+
## [1.9.381.0] - 2026-10-22
|
|
47
|
+
|
|
48
|
+
Ride: libleptris 1.9.381 — the XSLT identity/copy-of quadratic fix
|
|
49
|
+
(#1839): identity transforms 13.0 s -> 57 ms on a 20k-book catalog;
|
|
50
|
+
every CLI row now beats xsltproc end-to-end. The Rakefile pin moves
|
|
51
|
+
1.9.380 -> 1.9.381 in the same commit so the platform gems vendor
|
|
52
|
+
this core.
|
|
53
|
+
|
|
20
54
|
## [1.9.379.3] - 2026-10-22
|
|
21
55
|
|
|
22
56
|
### Fixed
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.382"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
|
@@ -401,8 +401,15 @@ namespace :audit do
|
|
|
401
401
|
.map { |n| n.sub(/\Aleptris_/, "") }.sort
|
|
402
402
|
attached = File.read("lib/leptris/xml/ffi.rb")
|
|
403
403
|
.scan(/attach_function :leptris_([a-z_0-9]+)/).flatten.sort
|
|
404
|
+
# Version-gated faces: soft-attached because engines BEFORE the
|
|
405
|
+
# named release lack the symbol (the load must survive them).
|
|
406
|
+
# They join the audit only when the engine pin reaches that
|
|
407
|
+
# release (the lockstep ride). TODO.concurrency/08-era law:
|
|
408
|
+
# never let one forward symbol poison the load.
|
|
409
|
+
version_gated = []
|
|
410
|
+
version_gated << "xsd_version" if Gem::Version.new(LIBLEPTRIS_VERSION) < Gem::Version.new("1.9.382")
|
|
404
411
|
unattached = exported - attached
|
|
405
|
-
unexported = attached - exported
|
|
412
|
+
unexported = attached - exported - version_gated
|
|
406
413
|
unless unattached.empty?
|
|
407
414
|
puts "exported but NOT attached (upstream surface drift):"
|
|
408
415
|
unattached.each { |s| puts " leptris_#{s}" }
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -2999,18 +2999,41 @@ static void *create_elem_core(void *doc_ptr, void *parent,
|
|
|
2999
2999
|
return elem;
|
|
3000
3000
|
}
|
|
3001
3001
|
|
|
3002
|
+
/* leptris-ruby#539: the ATTACHED construction faces are
|
|
3003
|
+
* wrapper-visible mutations — they splice a built child under a
|
|
3004
|
+
* parent — so they run the same freed/readonly gates and version
|
|
3005
|
+
* bump as every other write face (bump before the call: a failed
|
|
3006
|
+
* mutation merely discards memos). The raw-address face keeps the
|
|
3007
|
+
* legacy Integer-document contract (moxml's pre-#340 callers pass
|
|
3008
|
+
* doc.c_address): a bare engine address cannot reach the Document
|
|
3009
|
+
* object, so the gate runs only when the caller names it. */
|
|
3002
3010
|
static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
|
|
3003
3011
|
VALUE parent_addr, VALUE name,
|
|
3004
3012
|
VALUE attrs)
|
|
3005
3013
|
{
|
|
3006
|
-
void *doc_ptr
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
+
void *doc_ptr;
|
|
3015
|
+
void *parent;
|
|
3016
|
+
void *elem;
|
|
3017
|
+
|
|
3018
|
+
parent = NIL_P(parent_addr)
|
|
3019
|
+
? NULL
|
|
3020
|
+
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
3021
|
+
if (!NIL_P(document) && !RB_INTEGER_TYPE_P(document)) {
|
|
3022
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
3023
|
+
rb_raise(c_use_after_free_error,
|
|
3024
|
+
"owning document has been freed");
|
|
3025
|
+
/* The bump rides the ATTACH: a free-floating create mutates
|
|
3026
|
+
* no tree (detached-create discipline, no bump). */
|
|
3027
|
+
if (parent)
|
|
3028
|
+
document_advance_version(document);
|
|
3029
|
+
doc_ptr = doc_ptr_of(document);
|
|
3030
|
+
} else {
|
|
3031
|
+
doc_ptr = NIL_P(document)
|
|
3032
|
+
? NULL
|
|
3033
|
+
: (void *)(uintptr_t)NUM2ULL(document);
|
|
3034
|
+
}
|
|
3035
|
+
elem = create_elem_core(doc_ptr, parent,
|
|
3036
|
+
StringValueCStr(name), attrs);
|
|
3014
3037
|
return elem ? ULL2NUM((uintptr_t)elem) : Qnil;
|
|
3015
3038
|
}
|
|
3016
3039
|
|
|
@@ -3025,15 +3048,29 @@ static VALUE nf_create_element_with_attrs_wrapped(
|
|
|
3025
3048
|
VALUE self, VALUE document, VALUE parent_addr, VALUE name,
|
|
3026
3049
|
VALUE attrs)
|
|
3027
3050
|
{
|
|
3028
|
-
void *doc_ptr
|
|
3029
|
-
void *parent
|
|
3030
|
-
|
|
3031
|
-
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
3032
|
-
void *elem = create_elem_core(doc_ptr, parent,
|
|
3033
|
-
StringValueCStr(name), attrs);
|
|
3051
|
+
void *doc_ptr;
|
|
3052
|
+
void *parent;
|
|
3053
|
+
void *elem;
|
|
3034
3054
|
VALUE ck, key, node, cache;
|
|
3035
3055
|
|
|
3036
3056
|
(void)self;
|
|
3057
|
+
parent = NIL_P(parent_addr)
|
|
3058
|
+
? NULL
|
|
3059
|
+
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
3060
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
3061
|
+
rb_raise(c_use_after_free_error,
|
|
3062
|
+
"owning document has been freed");
|
|
3063
|
+
/* #539: attached construction bumps the version — the same
|
|
3064
|
+
* discipline as append_binding_child (bump before the call; a
|
|
3065
|
+
* failed mutation merely discards memos) — so version-stamped
|
|
3066
|
+
* memos (C content, walk-reads) retire across the splice.
|
|
3067
|
+
* Free-floating creates (parent_addr 0) mutate no tree and
|
|
3068
|
+
* stay bump-free, like the detached create faces. */
|
|
3069
|
+
if (parent)
|
|
3070
|
+
document_advance_version(document);
|
|
3071
|
+
doc_ptr = doc_ptr_of(document);
|
|
3072
|
+
elem = create_elem_core(doc_ptr, parent,
|
|
3073
|
+
StringValueCStr(name), attrs);
|
|
3037
3074
|
if (!elem) return Qnil;
|
|
3038
3075
|
resolve_binding_classes();
|
|
3039
3076
|
ck = doc_klass_for_kind(document, 0 /* element */);
|
|
@@ -3057,6 +3094,80 @@ static VALUE nf_create_element_with_attrs_wrapped(
|
|
|
3057
3094
|
return node;
|
|
3058
3095
|
}
|
|
3059
3096
|
|
|
3097
|
+
/* TODO.sota/04 (moxml build row): ONE construction crossing for the
|
|
3098
|
+
* consumer build shape — create + attrs + attach + optional text +
|
|
3099
|
+
* version bump + consumer mint, all in C off the PARENT WRAPPER's
|
|
3100
|
+
* struct (the document comes from the struct: no FFI twin, no
|
|
3101
|
+
* bridge, no per-element document climb on the Ruby side). Nokogiri's
|
|
3102
|
+
* equivalent shape is five separate crossings (Node.new, []= x2,
|
|
3103
|
+
* content=, add_child) + five wrapper allocs; this face is one
|
|
3104
|
+
* crossing + one wrapper. attrs arrives FLATTENED (name, value, ...)
|
|
3105
|
+
* like create_element_with_attrs_wrapped; text may be nil. */
|
|
3106
|
+
static VALUE nn_add_element_full(VALUE self, VALUE name, VALUE attrs,
|
|
3107
|
+
VALUE text)
|
|
3108
|
+
{
|
|
3109
|
+
struct native_node *pn;
|
|
3110
|
+
VALUE doc, ck, node;
|
|
3111
|
+
void *doc_ptr, *elem;
|
|
3112
|
+
int st;
|
|
3113
|
+
|
|
3114
|
+
resolve_binding_classes();
|
|
3115
|
+
TypedData_Get_Struct(self, struct native_node, &nn_type, pn);
|
|
3116
|
+
doc = pn->document;
|
|
3117
|
+
if (NIL_P(doc) || NIL_P(rb_ivar_get(doc, id_iv_c_address)))
|
|
3118
|
+
rb_raise(c_use_after_free_error,
|
|
3119
|
+
"owning document has been freed");
|
|
3120
|
+
if (rb_ivar_get(doc, id_iv_readonly) == Qtrue)
|
|
3121
|
+
rb_raise(c_readonly_error,
|
|
3122
|
+
"document is readonly — mutation attempted");
|
|
3123
|
+
/* Bump before the call: a failed construction merely discards
|
|
3124
|
+
* memos (the #539 discipline). */
|
|
3125
|
+
rb_ivar_set(doc, id_iv_version,
|
|
3126
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(doc, id_iv_version)) + 1));
|
|
3127
|
+
doc_ptr = doc_ptr_of(doc);
|
|
3128
|
+
elem = create_elem_core(doc_ptr, pn->ptr,
|
|
3129
|
+
StringValueCStr(name), attrs);
|
|
3130
|
+
if (!elem) return Qnil;
|
|
3131
|
+
if (!NIL_P(text)) {
|
|
3132
|
+
st = f_elem_set_text(elem, StringValueCStr(text));
|
|
3133
|
+
check_status_c(st);
|
|
3134
|
+
}
|
|
3135
|
+
ck = doc_klass_for_kind(doc, 0 /* element */);
|
|
3136
|
+
if (!NIL_P(ck)) {
|
|
3137
|
+
node = mint_consumer_node(ck, doc, elem);
|
|
3138
|
+
/* The #508 identity contract, born-primed from the parent
|
|
3139
|
+
* wrapper: @native is the node itself and the contract state
|
|
3140
|
+
* (@context/@adapter) copies off self, so the consumer's
|
|
3141
|
+
* per-child wrap prime disappears from the build row. */
|
|
3142
|
+
rb_ivar_set(node, id_iv_mx_native, node);
|
|
3143
|
+
if (NIL_P(rb_ivar_get(node, id_iv_mx_context))) {
|
|
3144
|
+
VALUE cx = rb_ivar_get(self, id_iv_mx_context);
|
|
3145
|
+
if (!NIL_P(cx)) {
|
|
3146
|
+
VALUE ad = rb_ivar_get(self, id_iv_mx_adapter);
|
|
3147
|
+
rb_ivar_set(node, id_iv_mx_context, cx);
|
|
3148
|
+
if (!NIL_P(ad)) rb_ivar_set(node, id_iv_mx_adapter, ad);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return node;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
node = rb_hash_aref(native_cache_of(doc),
|
|
3155
|
+
ULL2NUM((uint64_t)(uintptr_t)elem));
|
|
3156
|
+
if (!NIL_P(node)) return node;
|
|
3157
|
+
node = rb_obj_alloc(c_b_element);
|
|
3158
|
+
rb_iv_set(node, "@c_address", ULL2NUM((uint64_t)(uintptr_t)elem));
|
|
3159
|
+
rb_iv_set(node, "@document", doc);
|
|
3160
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
3161
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
3162
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
3163
|
+
rb_iv_set(node, "@pub_document", doc);
|
|
3164
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
3165
|
+
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
3166
|
+
rb_hash_aset(native_cache_of(doc),
|
|
3167
|
+
ULL2NUM((uint64_t)(uintptr_t)elem), node);
|
|
3168
|
+
return node;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3060
3171
|
static VALUE nf_attribute_rows(VALUE self, VALUE document, VALUE addr)
|
|
3061
3172
|
{
|
|
3062
3173
|
(void)self;
|
|
@@ -3502,6 +3613,18 @@ static VALUE nf_name(VALUE self, VALUE addr)
|
|
|
3502
3613
|
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
3503
3614
|
}
|
|
3504
3615
|
|
|
3616
|
+
/* moxml#460/#1855 ask 2: engine-side name comparison — no String
|
|
3617
|
+
* materialization for name-comparing consumers. Qtrue/Qfalse;
|
|
3618
|
+
* Qnil when the address is not an element (mirrors nf_name). */
|
|
3619
|
+
static VALUE nf_name_equals(VALUE self, VALUE addr, VALUE name)
|
|
3620
|
+
{
|
|
3621
|
+
const char *s;
|
|
3622
|
+
(void)self;
|
|
3623
|
+
s = f_elem_name((void *)(uintptr_t)NUM2ULL(addr));
|
|
3624
|
+
if (!s) return Qnil;
|
|
3625
|
+
return strcmp(s, StringValueCStr(name)) == 0 ? Qtrue : Qfalse;
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3505
3628
|
static VALUE nf_element_text(VALUE self, VALUE addr)
|
|
3506
3629
|
{
|
|
3507
3630
|
const char *s;
|
|
@@ -3928,6 +4051,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3928
4051
|
rb_define_alias(c_native_node, "text_read", "content");
|
|
3929
4052
|
rb_define_alias(c_native_node, "name_read", "name");
|
|
3930
4053
|
rb_define_method(c_native_node, "children", nn_children, 0);
|
|
4054
|
+
rb_define_method(c_native_node, "add_element_full",
|
|
4055
|
+
nn_add_element_full, 3);
|
|
3931
4056
|
rb_define_alias(c_native_node, "children_read", "children");
|
|
3932
4057
|
/* Unshadowed write face (the add_child_native pattern): the
|
|
3933
4058
|
* registered Identity klasses shadow content= with the
|
|
@@ -3944,6 +4069,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3944
4069
|
m_native = rb_define_module_under(m_xml, "Native");
|
|
3945
4070
|
m_native_sentinel = m_native;
|
|
3946
4071
|
rb_define_module_function(m_native, "fast_name", nf_name, 1);
|
|
4072
|
+
rb_define_module_function(m_native, "name_equals", nf_name_equals, 2);
|
|
3947
4073
|
id_sax_start_document = rb_intern("start_document");
|
|
3948
4074
|
id_sax_end_document = rb_intern("end_document");
|
|
3949
4075
|
id_sax_start_element = rb_intern("start_element");
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -548,11 +548,17 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
548
548
|
# Cross-document and docless (#376): full path first (lift +
|
|
549
549
|
# engine copy), then the source original is removed; returns
|
|
550
550
|
# the installed copy (self's new next sibling).
|
|
551
|
+
# #543: the raw-FFI lanes are wrapper-visible mutations too —
|
|
552
|
+
# they must advance the document version like the C-bound face,
|
|
553
|
+
# or every #530 walk-read memo warmed before the insert serves
|
|
554
|
+
# stale children afterwards (bump before the splice: a failed
|
|
555
|
+
# mutation merely discards memos).
|
|
551
556
|
if Leptris::XML::Element.foreign_to?(node, @document)
|
|
552
557
|
ensure_writable!
|
|
553
558
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
554
559
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
555
560
|
end
|
|
561
|
+
@document&.advance_version
|
|
556
562
|
Leptris::XML::FFI.check_status(
|
|
557
563
|
Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
|
|
558
564
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -569,6 +575,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
569
575
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
570
576
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
571
577
|
end
|
|
578
|
+
@document&.advance_version
|
|
572
579
|
Leptris::XML::FFI.check_status(
|
|
573
580
|
Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
|
|
574
581
|
node
|
|
@@ -580,12 +587,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
580
587
|
# the namespace lift — the full path below handles it.
|
|
581
588
|
# Cross-document and docless (#376): full path first (lift +
|
|
582
589
|
# engine copy), then the source original is removed; returns
|
|
583
|
-
# the installed copy (self's new previous sibling).
|
|
590
|
+
# the installed copy (self's new previous sibling). #543: the
|
|
591
|
+
# raw-FFI lanes advance the version like the C-bound face.
|
|
584
592
|
if Leptris::XML::Element.foreign_to?(node, @document)
|
|
585
593
|
ensure_writable!
|
|
586
594
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
587
595
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
588
596
|
end
|
|
597
|
+
@document&.advance_version
|
|
589
598
|
Leptris::XML::FFI.check_status(
|
|
590
599
|
Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
|
|
591
600
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -602,6 +611,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
602
611
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
603
612
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
604
613
|
end
|
|
614
|
+
@document&.advance_version
|
|
605
615
|
Leptris::XML::FFI.check_status(
|
|
606
616
|
Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
|
|
607
617
|
node
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -1397,6 +1397,24 @@ attach_function :leptris_parse_string,
|
|
|
1397
1397
|
[:leptris_xsd_schema], :size_t
|
|
1398
1398
|
attach_function :leptris_xsd_error,
|
|
1399
1399
|
[:leptris_xsd_schema], UTF8_STRING
|
|
1400
|
+
# libleptris 1.9.382 (moxml#460/#1841): the structured report —
|
|
1401
|
+
# rows of {kind, component, line, column, message}; schema-
|
|
1402
|
+
# owned memory, valid until the next validate. out receives
|
|
1403
|
+
# the record array pointer; the return is the row count.
|
|
1404
|
+
attach_function :leptris_xsd_error_report,
|
|
1405
|
+
[:leptris_xsd_schema, :pointer], :size_t
|
|
1406
|
+
# libleptris 1.9.382 (moxml#460 item 2): the schema's
|
|
1407
|
+
# effective version — "1.1" when the root declares
|
|
1408
|
+
# @version="1.1" (1.1 semantics apply), else "1.0". SOFT
|
|
1409
|
+
# attach: engines before 1.9.382 lack the symbol, and a hard
|
|
1410
|
+
# attach would break every face at load for the whole gem —
|
|
1411
|
+
# callers gate on FFI.respond_to?(:leptris_xsd_version).
|
|
1412
|
+
begin
|
|
1413
|
+
attach_function :leptris_xsd_version,
|
|
1414
|
+
[:leptris_xsd_schema], UTF8_STRING
|
|
1415
|
+
rescue ::FFI::NotFoundError
|
|
1416
|
+
nil
|
|
1417
|
+
end
|
|
1400
1418
|
# Slice 2 (libleptris 1.9.319, #1075): lexical validation.
|
|
1401
1419
|
# Built-ins take the "xs:NAME" reference spelling; -1 = not
|
|
1402
1420
|
# in the tier-1 table. simple_valid resolves a user
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
|
Binary file
|
data/lib/leptris/xml/xsd.rb
CHANGED
|
@@ -108,6 +108,20 @@ module Leptris::XML::XSD
|
|
|
108
108
|
Leptris::XML::FFI.leptris_xsd_declaration_count(@handle)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
# The schema's effective version (libleptris 1.9.382, moxml#460
|
|
112
|
+
# item 2): "1.1" when the root carries @version="1.1" — the
|
|
113
|
+
# engine then applies 1.1 semantics (xs:alternative CTA,
|
|
114
|
+
# xs:assert) — else "1.0". Consumers scope 1.0-only checks
|
|
115
|
+
# against it.
|
|
116
|
+
def version
|
|
117
|
+
check_released!
|
|
118
|
+
unless Leptris::XML::FFI.respond_to?(:leptris_xsd_version)
|
|
119
|
+
raise Leptris::XML::Error,
|
|
120
|
+
'Schema#version requires libleptris 1.9.382+ ' '(the leptris_xsd_version face)'
|
|
121
|
+
end
|
|
122
|
+
Leptris::XML::FFI.leptris_xsd_version(@handle) || '1.0'
|
|
123
|
+
end
|
|
124
|
+
|
|
111
125
|
# The engine's schema-level error message, or nil when the
|
|
112
126
|
# schema compiled cleanly.
|
|
113
127
|
def error
|
|
@@ -150,6 +164,46 @@ module Leptris::XML::XSD
|
|
|
150
164
|
end
|
|
151
165
|
end
|
|
152
166
|
|
|
167
|
+
# The structured report (libleptris 1.9.382, moxml#460/#1841):
|
|
168
|
+
# one hash per finding — kind (element-decl / attribute-use /
|
|
169
|
+
# content-model / simpleType-facet / type-resolution /
|
|
170
|
+
# assertion / identity-constraint), component (declaring
|
|
171
|
+
# schema component), line, column, message (the same text
|
|
172
|
+
# validate_errors carries). Requires the 1.9.382 engine.
|
|
173
|
+
StructuredError = Struct.new(:kind, :component, :line, :column,
|
|
174
|
+
:message)
|
|
175
|
+
|
|
176
|
+
def structured_errors(document)
|
|
177
|
+
check_released!
|
|
178
|
+
valid?(document)
|
|
179
|
+
unless Leptris::XML::FFI.respond_to?(:leptris_xsd_error_report)
|
|
180
|
+
raise Leptris::XML::Error,
|
|
181
|
+
'structured_errors requires libleptris 1.9.382+ ' \
|
|
182
|
+
'(the leptris_xsd_error_report face)'
|
|
183
|
+
end
|
|
184
|
+
out = ::FFI::MemoryPointer.new(:pointer)
|
|
185
|
+
n = Leptris::XML::FFI.leptris_xsd_error_report(@handle, out)
|
|
186
|
+
rows = out.get_pointer(0)
|
|
187
|
+
return [] if n.zero? || rows.null?
|
|
188
|
+
|
|
189
|
+
# C layout: kind* / component* / uint line / uint column /
|
|
190
|
+
# message* — 32 bytes per record with tail padding.
|
|
191
|
+
(0...n).map do |i|
|
|
192
|
+
ptr = rows + (i * 32)
|
|
193
|
+
kind = ptr.get_pointer(0)
|
|
194
|
+
component = ptr.get_pointer(8)
|
|
195
|
+
line = ptr.get_uint32(16)
|
|
196
|
+
column = ptr.get_uint32(20)
|
|
197
|
+
message = ptr.get_pointer(24)
|
|
198
|
+
StructuredError.new(
|
|
199
|
+
kind.nil? ? nil : kind.read_string,
|
|
200
|
+
component.nil? ? nil : component.read_string,
|
|
201
|
+
line, column,
|
|
202
|
+
message.nil? ? nil : message.read_string
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
153
207
|
# Slice 3 direct face: a child SEQUENCE against a top-level
|
|
154
208
|
# element declaration's compiled content model (occurrence
|
|
155
209
|
# bounds, xs:any wildcards). +children+ are child Nodes (name
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.382] - 2026-10-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- category escapes — \p{L}, \p{Nd}, families (moxml#464) (xsd)
|
|
8
|
+
- codepoint-wide \p{Is...} blocks — the full F.1 table (moxml#464) (xsd)
|
|
9
|
+
- \p{Is...} block escapes in the pattern-facet regex (moxml#464) (xsd)
|
|
10
|
+
- XSD 1.1 negative wildcards — notQName on xs:any and xs:anyAttribute (moxml#460) (xsd)
|
|
11
|
+
- leptris_xsd_version — effective version reporting (moxml#460) (xsd)
|
|
12
|
+
- structured error rows — {kind, component, line, column, message} (xsd)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- gate the category table on LEPTRIS_HAS_UTF8PROC (simd)
|
|
17
|
+
- CAS-only retain spinlock — the plain spin read raced the atomics (memory)
|
|
18
|
+
- dispatch_init fully initializes the count3_high slot on every arch (simd)
|
|
19
|
+
- declared non-UTF-8 encodings are EXEMPT from the fused gate (parse)
|
|
20
|
+
|
|
21
|
+
### Performance
|
|
22
|
+
|
|
23
|
+
- parse_string adopts the conversion buffer — the copy-in chain and the pristine carve drop out (#1846)
|
|
24
|
+
- raise the element-index build threshold to 8 axis queries (xpath)
|
|
25
|
+
- fuse the UTF-8 fast gate into the count3 sizing pass (parse)
|
|
26
|
+
- rank unmutated documents by parse-time byte offset (xpath)
|
|
27
|
+
- pre-size the element-position journal from the count3 estimate (parse)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## [1.9.381] - 2026-10-22
|
|
32
|
+
|
|
33
|
+
The XSLT transform engine is no longer slower than libxslt on the
|
|
34
|
+
workload users actually run. The identity transform
|
|
35
|
+
(`match="@*|node()"` + `xsl:copy`) was quadratic: 13.0 s where
|
|
36
|
+
xsltproc takes 65 ms on a 20k-book catalog. Three independent causes
|
|
37
|
+
went in PR #1839, and every measured CLI row now beats xsltproc
|
|
38
|
+
end-to-end:
|
|
39
|
+
|
|
40
|
+
| stylesheet (20k-book catalog) | xsltproc 1.1.35 | 1.9.380 | 1.9.381 |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| identity `@*|node()` | 64.5 ms | 13,002 ms | **56.9 ms** |
|
|
43
|
+
| `copy-of //book` | 37.2 ms | 58.8 ms | **25.3 ms** |
|
|
44
|
+
| dispatch (apply-templates) | 63.8 ms | 23.5 ms | **23.6 ms** |
|
|
45
|
+
| 1,000-template sheet | 293.8 ms | 18.7 ms | **17.6 ms** |
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- apply xsi:type at every depth in whole-instance validation: an
|
|
50
|
+
instance document may pin a derived type on any element via
|
|
51
|
+
`xsi:type`; validation previously honored it only at the root, so
|
|
52
|
+
deeper particles validated against the declared type (#1838)
|
|
53
|
+
|
|
54
|
+
### Performance
|
|
55
|
+
|
|
56
|
+
- **xslt: identity transforms and copy-of were quadratic (#1839).**
|
|
57
|
+
Three fixes: (1) the pattern fast path never compiled node-type
|
|
58
|
+
tests — `node(`/`text(`/`processing-instruction(` parsed as NCName
|
|
59
|
+
and failed on the paren, sending every match through the quadratic
|
|
60
|
+
ancestor ladder (and `processing-instruction(` compared 21 of its
|
|
61
|
+
23 prefix characters); (2) the document-order rank table hashed
|
|
62
|
+
pointers by low bits, which degenerates on fixed-stride pool
|
|
63
|
+
allocations (872 ms to build for a 500k-node transform); (3) the
|
|
64
|
+
append-tail pair pointed at the deepest node a body built, so wide
|
|
65
|
+
parents re-walked their whole child chain on every append after an
|
|
66
|
+
interleaved child. Full suite 2103/2103, libxslt-205 output parity
|
|
67
|
+
included.
|
|
68
|
+
- dom: lane-18 append/create micro-levers from a fresh profile
|
|
69
|
+
(#1836)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
3
73
|
## [1.9.380] - 2026-10-22
|
|
4
74
|
|
|
5
75
|
### Fixed
|
|
@@ -106,7 +106,11 @@ Harness `wt/460-harness/build_bench.rb`; A/B pairs alternate within
|
|
|
106
106
|
ONE window (drift ±20%). Rejected within noise: fast_attach,
|
|
107
107
|
native-cache deletion, doc_klass memo, identity-probe deletion,
|
|
108
108
|
add_child alias. Residual: wrapper allocation (owner-level node
|
|
109
|
-
layout).
|
|
109
|
+
layout). **Allocation census 2026-10-22: AT FLOOR** — moxml
|
|
110
|
+
0.06 minor GC/build vs nokogiri 0.39 (6.5x fewer), 3420 objects vs
|
|
111
|
+
9907; every named suspect eliminated by the levers; the non-floor
|
|
112
|
+
remainder is 17 objects/build (~0.1% CPU). No lever remains — the
|
|
113
|
+
gap is the wrapper-count/identity-contract decision.
|
|
110
114
|
- [ ] per-element object budget: one Ruby object per created node
|
|
111
115
|
(drop the shell entirely post-create-priming; the mint cache
|
|
112
116
|
insert policy; the context registration)
|
|
@@ -138,7 +142,19 @@ three interleaved-median runs (`wt/460-harness/pipeline_bench.rb`,
|
|
|
138
142
|
build, serialize medians, fail on >5% regression)
|
|
139
143
|
|
|
140
144
|
### B5 remaining red rows
|
|
141
|
-
- [
|
|
145
|
+
- [x] pugixml lane-18 append/attr-parse — **re-dated 2026-10-22: the
|
|
146
|
+
2.0x/5.9x rows no longer exist.** The named levers shipped in
|
|
147
|
+
the prior 22-round campaign (dup-check hash PR #1413, FNV-fused
|
|
148
|
+
lookup PR #1425, append parity PR #1533). Attr-lookup
|
|
149
|
+
~parity (0.99x; caveat: in-process rigs let leptris's teardown
|
|
150
|
+
depress the adjacent pugixml row — self-warmed rigs are the
|
|
151
|
+
honest numbers). Append: **1.44x on self-warmed rigs; 1.37x
|
|
152
|
+
after PR leptris#1836** (4 behavior-identical levers: childless-
|
|
153
|
+
proof ancestor-walk skip, fused type chains, bounded name scan,
|
|
154
|
+
create_pooled double-copy fix; −3.6%, 9/10 pairs). Residual
|
|
155
|
+
~1.37x parse gap = conformance features pugixml doesn't do
|
|
156
|
+
(dup detection, error positions, NUL writes) — the measured
|
|
157
|
+
floor; owner decision in #1436.
|
|
142
158
|
- [ ] zero-copy text lever (#1218) — adopt if it wins any row
|
|
143
159
|
|
|
144
160
|
## Gate C — Safety: trust is auditable
|
|
@@ -28,3 +28,7 @@ TODO.max-perf/node-layout-fork.md — 5 slices, addresses #1222 +
|
|
|
28
28
|
The interleaved-parse rewrite (owner-directed 2026-09-20):
|
|
29
29
|
TODO.max-perf/interleaved-parse.md — the #1222 step-change; slices 0-4,
|
|
30
30
|
each CI-benchmark-gated.
|
|
31
|
+
|
|
32
|
+
The C-face vs pugixml campaign (serialbench fair-flags wave, 2026-10-24):
|
|
33
|
+
TODO.max-perf/15-c-face-vs-pugixml.md — parse 2.81x / generation 2.23x /
|
|
34
|
+
xpath 3.48x; three tracks, CI-bench-gated. Track A1 shipped (PR #1844).
|