leptris 1.9.380.0 → 1.9.380.2

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Rakefile +1 -1
  4. data/ext/leptris/native/native.c +168 -7
  5. data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
  6. data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
  7. data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
  8. data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
  9. data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
  10. data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
  11. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  12. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  13. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  14. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  15. data/lib/leptris/version.rb +1 -1
  16. data/lib/leptris/xml/native_layer.rb +20 -2
  17. data/vendor-src/README.md +1 -1
  18. data/vendor-src/libleptris/CHANGELOG.md +10 -0
  19. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  20. data/vendor-src/libleptris/TODO.SOTA.md +82 -56
  21. data/vendor-src/libleptris/benchmarks/baseline.json +1 -0
  22. data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
  23. data/vendor-src/libleptris/bindings/rust/descriptor/Cargo.toml +1 -1
  24. data/vendor-src/libleptris/cli/output.c +11 -0
  25. data/vendor-src/libleptris/docs/MISSION-PLAN.md +12 -9
  26. data/vendor-src/libleptris/docs/guide/memory-model.md +229 -0
  27. data/vendor-src/libleptris/docs/guide/migrating-from-libxml2.md +131 -0
  28. data/vendor-src/libleptris/packaging/README.md +120 -0
  29. data/vendor-src/libleptris/packaging/conan/conanfile.py +120 -0
  30. data/vendor-src/libleptris/packaging/conan/test_package/CMakeLists.txt +7 -0
  31. data/vendor-src/libleptris/packaging/conan/test_package/conanfile.py +27 -0
  32. data/vendor-src/libleptris/packaging/conan/test_package/src/example.c +22 -0
  33. data/vendor-src/libleptris/packaging/debian/README.md +87 -0
  34. data/vendor-src/libleptris/packaging/debian/changelog +8 -0
  35. data/vendor-src/libleptris/packaging/debian/control +55 -0
  36. data/vendor-src/libleptris/packaging/debian/copyright +26 -0
  37. data/vendor-src/libleptris/packaging/debian/leptris.install +1 -0
  38. data/vendor-src/libleptris/packaging/debian/libleptris-dev.install +5 -0
  39. data/vendor-src/libleptris/packaging/debian/libleptris1.install +1 -0
  40. data/vendor-src/libleptris/packaging/debian/rules +34 -0
  41. data/vendor-src/libleptris/packaging/debian/source/format +1 -0
  42. data/vendor-src/libleptris/packaging/homebrew/leptris.rb +75 -0
  43. data/vendor-src/libleptris/scripts/perf_gate.py +51 -0
  44. data/vendor-src/libleptris/scripts/verify_manifest.py +182 -0
  45. data/vendor-src/libleptris/src/leptris/dtd/content_check.c +31 -23
  46. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +52 -8
  47. data/vendor-src/libleptris/src/leptris/xinclude/xinclude.c +359 -244
  48. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +17 -11
  49. data/vendor-src/libleptris/src/leptris/xsd/compile.c +110 -7
  50. data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +14 -4
  51. data/vendor-src/libleptris/src/leptris/xsd/validate.c +179 -19
  52. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +4 -0
  53. data/vendor-src/libleptris/test/differential/README.md +100 -0
  54. data/vendor-src/libleptris/test/differential/corpus/basic.xml +13 -0
  55. data/vendor-src/libleptris/test/differential/corpus/cdata.xml +5 -0
  56. data/vendor-src/libleptris/test/differential/corpus/comments_pis.xml +1 -0
  57. data/vendor-src/libleptris/test/differential/corpus/dtd_model.xml +1 -0
  58. data/vendor-src/libleptris/test/differential/corpus/entities_dtd.xml +1 -0
  59. data/vendor-src/libleptris/test/differential/corpus/mixed_deep.xml +1 -0
  60. data/vendor-src/libleptris/test/differential/corpus/namespaces.xml +11 -0
  61. data/vendor-src/libleptris/test/differential/corpus/xpath_target.xml +1 -0
  62. data/vendor-src/libleptris/test/differential/package.json +9 -0
  63. data/vendor-src/libleptris/test/differential/run.mjs +52 -0
  64. data/vendor-src/libleptris/test/differential/run.py +61 -0
  65. data/vendor-src/libleptris/test/differential/run.rb +48 -0
  66. data/vendor-src/libleptris/test/dtd/test_dtd_validate.cpp +39 -0
  67. data/vendor-src/libleptris/test/xinclude/test_xinclude.cpp +274 -10
  68. data/vendor-src/libleptris/test/xpath/test_xpath.cpp +5 -0
  69. data/vendor-src/libleptris/test/xquery/qt3-dispositions/README.md +47 -0
  70. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-deep-eq.md +187 -0
  71. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-kinds-misc.md +48 -0
  72. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-type.md +285 -0
  73. data/vendor-src/libleptris/test/xquery/qt3-dispositions/dep-gates.md +57 -0
  74. data/vendor-src/libleptris/test/xquery/qt3-dispositions/empty-query.md +11 -0
  75. data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-inline-no-params.md +48 -0
  76. data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-unsupported.md +143 -0
  77. data/vendor-src/libleptris/test/xquery/qt3-dispositions/error-channel.md +782 -0
  78. data/vendor-src/libleptris/test/xquery/qt3-dispositions/named-exclude.md +85 -0
  79. data/vendor-src/libleptris/test/xquery/qt3-dispositions/query-exclude.md +379 -0
  80. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +192 -26
  81. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +524 -2
  82. data/vendor-src/libleptris/test/xsd/xsd-dispositions/README.md +59 -0
  83. data/vendor-src/libleptris/vcpkg.json +1 -1
  84. metadata +46 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccc2364b66198cffb6a8c5e3d297ec702527af36dd81967fdfaf3e9e06427c7f
4
- data.tar.gz: 6949f57cb88a76163807e867dc693a1bb4aa98a8aca21261396cdd1c5cd235e0
3
+ metadata.gz: 213b5096ca73813ee05e8816632f5f3f0e0cbae5744b477b5cecb7acae15ad2b
4
+ data.tar.gz: 23ea9d5f3ddb5f067e5bc90b29599e47df1418336bff8caa1b3fb139202079fe
5
5
  SHA512:
6
- metadata.gz: 92ae7adab3e90040f2966d0229889ee2c5f88a48dfcf12eb7970fd8da442c9c14de1fd0d4006e74cd9994da680eb3027014ee327a8a467e58924383273183db4
7
- data.tar.gz: 1671aa48103b3229f27bb4949074f9596d2d260f473587f4c16cd6956c4c70827685f822dd58139f90106a40d7ef539ea722393f95220a6bdfd82072ffb1420a
6
+ metadata.gz: ca38fd56c66cf502830190aed494c9371571477c2d4609d93dcc942ff98b7c34503ffe34f61dda5148aa63306f1386b4b99c022d587be5cb6b19e79e103ef8f2
7
+ data.tar.gz: 07ff974cc3fee133d576d76cbf205422235d6cc7656ade52f0d50453c8cc0232f6d648969e6aa2d3778f15e3417f1a30b75b07e93e8124054b75b47a0c876f62
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [Unreleased]
2
+
3
+ ### Fixed — engine ride
4
+
5
+ - **Ride libleptris 1.9.380**: the xsi:type conformance fix
6
+ (leptris/leptris#1832, lutaml/moxml#433) — an element carrying
7
+ `xsi:type` is assessed against the NAMED type (XSD 1.0
8
+ §2.6.2/§3.3.4), so valid xsi:type-derived instances validate
9
+ instead of being rejected against the declared type's content
10
+ model. Released 1.9.380.0 with the pin still at 1.9.379 (the
11
+ vendored dylib missed the fix); this pin bump is the real ride.
12
+
13
+ ### Fixed — engine ride (1.9.380.2)
14
+
15
+ - **Bulk XPath priming face rides (leptris/leptris-ruby#537)**:
16
+ `xpath_result_rows_bulk` gains the mx_context/mx_adapter birth-prime
17
+ parameters and marks all-native results; 1.9.380.1 shipped before
18
+ the PR merged, so the face rides in 1.9.380.2.
19
+
1
20
  ## [1.9.379.3] - 2026-10-22
2
21
 
3
22
  ### 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.379"
11
+ LIBLEPTRIS_VERSION = "1.9.380"
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
@@ -203,6 +203,10 @@ static VALUE binding_klass_for(int kind);
203
203
  static VALUE c_iteration_scope;
204
204
  static VALUE c_leptris_error;
205
205
  static VALUE c_b_document, c_b_freed, c_ffi_pointer;
206
+ /* BulkIdentityRows resolves lazily like the binding classes: the
207
+ * module lives in native_layer.rb, which optional consumers load. */
208
+ static VALUE c_bulk_rows_mark = Qundef;
209
+ static ID id_bulk_rows_mark;
206
210
  static ID id_ptr_new, id_freed_new, id_alive;
207
211
  static ID id_iv_c_address, id_iv_document, id_iv_parent;
208
212
  static ID id_iv_mx_context, id_iv_mx_adapter, id_iv_mx_native;
@@ -221,8 +225,23 @@ struct native_node {
221
225
  * Slot beats the hash memo on repeat reads of one name. */
222
226
  VALUE attr_key, attr_val, attr_ver;
223
227
  VALUE content_val, content_ver;
228
+ /* Walk-read memos (leptris-ruby#530): the element name and the
229
+ * children list, the two reads a tree walk pays per node. The
230
+ * children memo is what the subtree prime (#527 machinery)
231
+ * fills; the version stamp retires both on any mutation. The
232
+ * children list is SHARED with callers until the version moves
233
+ * (the mutation that would invalidate it also retires the
234
+ * memo) — treat it as read-only, like an interned String. */
235
+ VALUE name_val, name_ver;
236
+ VALUE children_val, children_ver;
224
237
  };
225
238
 
239
+ static VALUE columnar_node_for(VALUE document, VALUE owner_klass,
240
+ void *ptr, int kind);
241
+ static VALUE doc_klass_for_kind(VALUE document, int kind);
242
+ static VALUE nn_prime_children_of(VALUE document, VALUE owner_klass,
243
+ void *node, VALUE ver);
244
+
226
245
  static void nn_mark(void *p)
227
246
  {
228
247
  struct native_node *n = p;
@@ -234,6 +253,10 @@ static void nn_mark(void *p)
234
253
  rb_gc_mark(n->attr_val);
235
254
  if (n->content_val != Qnil)
236
255
  rb_gc_mark(n->content_val);
256
+ if (n->name_val != Qnil)
257
+ rb_gc_mark(n->name_val);
258
+ if (n->children_val != Qnil)
259
+ rb_gc_mark(n->children_val);
237
260
  }
238
261
 
239
262
  static size_t nn_size(const void *p) { (void)p; return sizeof(struct native_node); }
@@ -367,7 +390,19 @@ static VALUE nn_name(VALUE self)
367
390
  {
368
391
  struct native_node *n;
369
392
  const char *s;
393
+ VALUE doc, ver;
394
+
370
395
  TypedData_Get_Struct(self, struct native_node, &nn_type, n);
396
+ /* Walk-memo hit (leptris-ruby#530): a repeat name read is two
397
+ * struct reads and a Fixnum compare — the subtree prime fills
398
+ * this slot ahead of the walk, so walked names never recompute
399
+ * (or re-intern) until the next mutation. */
400
+ doc = n->document;
401
+ if (doc != Qnil) {
402
+ ver = rb_ivar_get(doc, id_iv_version);
403
+ if (n->name_ver == ver)
404
+ return n->name_val;
405
+ }
371
406
  s = f_elem_name(n->ptr);
372
407
  /* Interned (shared, frozen) fstring: element names repeat
373
408
  * massively across a document — one RString per distinct name
@@ -375,7 +410,13 @@ static VALUE nn_name(VALUE self)
375
410
  * ~18% of consumer materialize allocations were fresh name
376
411
  * strings). Names are identifiers; nothing downstream mutates
377
412
  * them in place (renames mint new strings). */
378
- return s ? rb_enc_interned_str(s, strlen(s), rb_utf8_encoding()) : Qnil;
413
+ VALUE name = s ? rb_enc_interned_str(s, strlen(s), rb_utf8_encoding())
414
+ : Qnil;
415
+ if (doc != Qnil) {
416
+ n->name_val = name;
417
+ n->name_ver = ver;
418
+ }
419
+ return name;
379
420
  }
380
421
 
381
422
  static VALUE nn_content(VALUE self)
@@ -563,14 +604,43 @@ static VALUE nn_children(VALUE self)
563
604
  void **buf;
564
605
  int *kinds;
565
606
  int count, i;
566
- VALUE cache, out, owner_klass;
607
+ VALUE cache, out, owner_klass, doc, ver;
567
608
 
568
609
  TypedData_Get_Struct(self, struct native_node, &nn_type, n);
610
+ /* Walk-memo hit (leptris-ruby#530): the subtree prime (or an
611
+ * earlier read) filled this node's list against the current
612
+ * mutation version — serve it, engine walk skipped. */
613
+ doc = n->document;
614
+ if (doc != Qnil) {
615
+ ver = rb_ivar_get(doc, id_iv_version);
616
+ if (n->children_ver == ver)
617
+ return n->children_val;
618
+ /* Registered-consumer documents (#340 doc-klass map = the
619
+ * moxml mint registration) walk whole subtrees top-down:
620
+ * the FIRST cold children_read trades itself for ONE
621
+ * subtree crossing that fills every node's name + children
622
+ * memo (walk_columnar machinery, native-cache identity —
623
+ * the primed wrappers ARE the answers later reads give).
624
+ * Unregistered documents keep the per-node fetch; both
625
+ * paths memo-store below. */
626
+ if (!NIL_P(doc_klass_for_kind(doc, NT_ELEMENT))) {
627
+ out = nn_prime_children_of(doc, rb_obj_class(self), n->ptr,
628
+ ver);
629
+ n->children_val = out;
630
+ n->children_ver = ver;
631
+ return out;
632
+ }
633
+ }
569
634
  count = fetch_children_two_call(n->ptr, &buf, &kinds);
570
635
  if (count <= 0) {
571
636
  ruby_xfree(buf);
572
637
  ruby_xfree(kinds);
573
- return rb_ary_new2(0);
638
+ out = rb_ary_new2(0);
639
+ if (doc != Qnil) {
640
+ n->children_val = out;
641
+ n->children_ver = ver;
642
+ }
643
+ return out;
574
644
  }
575
645
 
576
646
  cache = native_cache_of(n->document);
@@ -592,6 +662,10 @@ static VALUE nn_children(VALUE self)
592
662
  }
593
663
  ruby_xfree(buf);
594
664
  ruby_xfree(kinds);
665
+ if (doc != Qnil) {
666
+ n->children_val = out;
667
+ n->children_ver = ver;
668
+ }
595
669
  return out;
596
670
  }
597
671
 
@@ -1940,18 +2014,28 @@ static VALUE nf_at_xpath_first(VALUE self, VALUE document,
1940
2014
  * Nodesets free the handle here (TODO.perf/22 eager discipline);
1941
2015
  * non-nodeset results return Qnil UNFREED — the caller keeps the
1942
2016
  * scalar path, whose wrap owns the free. */
1943
- static VALUE nf_xpath_result_rows_bulk(VALUE self, VALUE document,
1944
- VALUE result_addr)
2017
+ static VALUE nf_xpath_result_rows_bulk(int argc, VALUE *argv, VALUE self)
1945
2018
  {
1946
- void *result = (void *)(uintptr_t)NUM2ULL(result_addr);
2019
+ VALUE document, result_addr, mx_context, mx_adapter;
2020
+ void *result;
1947
2021
  void **buf;
1948
2022
  int *kinds;
1949
2023
  size_t scount;
1950
2024
  long i, n;
1951
2025
  VALUE cache, out;
2026
+ int all_native;
1952
2027
 
1953
2028
  (void)self;
1954
2029
  resolve_binding_classes();
2030
+ /* Optional (context, adapter): when the consumer passes its
2031
+ * contract state, every registered-klass row is born primed
2032
+ * (@native is the node itself, @context/@adapter written here)
2033
+ * and an all-native result extends with BulkIdentityRows —
2034
+ * the consumer's per-row wrap prime disappears from the
2035
+ * iterate-every-result lane (same triple as nn_create_element). */
2036
+ rb_scan_args(argc, argv, "22", &document, &result_addr,
2037
+ &mx_context, &mx_adapter);
2038
+ result = (void *)(uintptr_t)NUM2ULL(result_addr);
1955
2039
  if (f_xp_type(result) != 0) /* XPATH_NODESET only */
1956
2040
  return Qnil;
1957
2041
 
@@ -1969,6 +2053,7 @@ static VALUE nf_xpath_result_rows_bulk(VALUE self, VALUE document,
1969
2053
  n = (long)scount;
1970
2054
  out = rb_ary_new_capa(n);
1971
2055
  cache = binding_cache_of(document);
2056
+ all_native = !NIL_P(mx_context);
1972
2057
  for (i = 0; i < n; i++) {
1973
2058
  void *ptr = buf[i];
1974
2059
  int tag;
@@ -1988,10 +2073,17 @@ static VALUE nf_xpath_result_rows_bulk(VALUE self, VALUE document,
1988
2073
  node = rb_hash_aref(native_cache_of(document), key);
1989
2074
  if (NIL_P(node))
1990
2075
  node = mint_consumer_node(mk, document, ptr);
2076
+ if (all_native &&
2077
+ !rb_ivar_defined(node, id_iv_mx_context)) {
2078
+ rb_ivar_set(node, id_iv_mx_native, node);
2079
+ rb_ivar_set(node, id_iv_mx_context, mx_context);
2080
+ rb_ivar_set(node, id_iv_mx_adapter, mx_adapter);
2081
+ }
1991
2082
  rb_ary_push(out, node);
1992
2083
  continue;
1993
2084
  }
1994
2085
  }
2086
+ all_native = 0;
1995
2087
  node = materialize_xp_entry(document, cache, result, ptr,
1996
2088
  kinds[i], (int)i);
1997
2089
  if (node == m_native_sentinel) {
@@ -2005,6 +2097,22 @@ static VALUE nf_xpath_result_rows_bulk(VALUE self, VALUE document,
2005
2097
  ruby_xfree(buf);
2006
2098
  ruby_xfree(kinds);
2007
2099
  f_xp_free(result);
2100
+ if (all_native) {
2101
+ /* Certificate for the consumer's fast row loop: every row
2102
+ * is a born-primed, registered-klass identity native. The
2103
+ * module lives in the Ruby layer (native_layer.rb) and may
2104
+ * not be loaded on faces used without it — an unresolved
2105
+ * mark degrades to the unmarked Array, never an error. */
2106
+ if (c_bulk_rows_mark == Qundef) {
2107
+ VALUE m_xml = rb_path2class("Leptris::XML");
2108
+ c_bulk_rows_mark =
2109
+ rb_const_defined_at(m_xml, id_bulk_rows_mark)
2110
+ ? rb_const_get_at(m_xml, id_bulk_rows_mark)
2111
+ : Qnil;
2112
+ }
2113
+ if (c_bulk_rows_mark != Qnil)
2114
+ rb_extend_object(out, c_bulk_rows_mark);
2115
+ }
2008
2116
  return out;
2009
2117
  }
2010
2118
 
@@ -2599,6 +2707,58 @@ static void walk_columnar_impl(VALUE document, VALUE owner_klass,
2599
2707
  }
2600
2708
  }
2601
2709
 
2710
+ /* ---- Subtree memo priming (leptris-ruby#530) ---------------------
2711
+ * Registered-consumer documents (the #340 doc-klass map — moxml's
2712
+ * mint registration) walk whole subtrees top-down, so the FIRST
2713
+ * cold children_read fills every ELEMENT's name + children read
2714
+ * memos in ONE crossing, reusing the walk_columnar machinery and
2715
+ * minting through columnar_node_for (native-cache identity — the
2716
+ * primed wrappers ARE the answers later reads hand back). Memos
2717
+ * are version-stamped like the content memo: any mutation bumps
2718
+ * @version and retires them subtree-wide.
2719
+ *
2720
+ * Conservative by construction: only ELEMENT descendants get their
2721
+ * memos filled here (the one kind whose f_first_child chain IS its
2722
+ * children list, the #527 contract); leaf and shell kinds keep the
2723
+ * per-node fetch, which memo-stores the same way. The receiver's
2724
+ * own name memo is left to nn_name (one node, once). */
2725
+ static VALUE nn_prime_children_of(VALUE document, VALUE owner_klass,
2726
+ void *node, VALUE ver)
2727
+ {
2728
+ VALUE out = rb_ary_new();
2729
+ void *c;
2730
+
2731
+ for (c = f_first_child(node); c; c = f_next_sibling(c)) {
2732
+ int kind = f_node_type(c);
2733
+ VALUE wrapper = columnar_node_for(document, owner_klass, c, kind);
2734
+ struct native_node *nn;
2735
+
2736
+ TypedData_Get_Struct(wrapper, struct native_node, &nn_type, nn);
2737
+ if (kind == WS_NODE_ELEMENT) {
2738
+ const char *name = f_elem_name(c);
2739
+ nn->name_val = name ? rb_enc_interned_str(name, strlen(name),
2740
+ rb_utf8_encoding())
2741
+ : Qnil;
2742
+ nn->name_ver = ver;
2743
+ nn->children_val = nn_prime_children_of(document, owner_klass,
2744
+ c, ver);
2745
+ nn->children_ver = ver;
2746
+ } else if (kind != WS_NODE_DOCTYPE && kind != 9 /* DOCUMENT */) {
2747
+ /* leaf kinds: memo the name exactly as nn_name computes
2748
+ * it (f_elem_name answers "" for nameless kinds); their
2749
+ * (empty) children keep the fetch path, which
2750
+ * memo-stores the same way. */
2751
+ const char *name = f_elem_name(c);
2752
+ nn->name_val = name ? rb_enc_interned_str(name, strlen(name),
2753
+ rb_utf8_encoding())
2754
+ : Qnil;
2755
+ nn->name_ver = ver;
2756
+ }
2757
+ rb_ary_push(out, wrapper);
2758
+ }
2759
+ return out;
2760
+ }
2761
+
2602
2762
  static VALUE nf_walk_columnar(VALUE self, VALUE root)
2603
2763
  {
2604
2764
  struct native_node *n;
@@ -3715,6 +3875,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3715
3875
  id_iv_mx_context = rb_intern("@context");
3716
3876
  id_iv_mx_native = rb_intern("@native");
3717
3877
  id_iv_mx_adapter = rb_intern("@adapter");
3878
+ id_bulk_rows_mark = rb_intern("BulkIdentityRows");
3718
3879
  id_iv_parent = rb_intern("@parent");
3719
3880
  id_iv_structure_memoizable = rb_intern("@structure_memoizable");
3720
3881
  id_iv_native_fast = rb_intern("@native_fast");
@@ -3823,7 +3984,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3823
3984
  rb_define_module_function(m_native, "materialize_xpath",
3824
3985
  nf_materialize_xpath, 2);
3825
3986
  rb_define_module_function(m_native, "xpath_result_rows_bulk",
3826
- nf_xpath_result_rows_bulk, 2);
3987
+ nf_xpath_result_rows_bulk, -1);
3827
3988
  rb_define_module_function(m_native, "set_binding_name",
3828
3989
  nf_set_binding_name, 3);
3829
3990
  rb_define_module_function(m_native, "set_binding_text",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.380.0"
4
+ VERSION = "1.9.380.2"
5
5
  end
@@ -59,6 +59,19 @@ rescue LoadError => e
59
59
  MSG
60
60
  end
61
61
 
62
+ # Certificate module for the bulk XPath rows face: an Array extended
63
+ # with this carries ONLY born-primed registered-klass identity natives
64
+ # (xpath_result_rows_bulk answered with a consumer context+adapter, so
65
+ # @native is the node itself and @context/@adapter are already set).
66
+ # Consumers with a per-row wrap in their result loop can key a fast
67
+ # path on it (moxml's NodeSet#each); one object per native address
68
+ # remains the binding's native-cache contract (#529) either way.
69
+ module Leptris::XML::BulkIdentityRows
70
+ def bulk_identity_rows?
71
+ true
72
+ end
73
+ end
74
+
62
75
  module Leptris::XML::Native
63
76
  # FFI.libleptris_candidates is the single source of truth — the
64
77
  # native layer MUST dlsym the exact image the FFI layer loaded,
@@ -118,10 +131,15 @@ class Leptris::XML::Document
118
131
  # (nodesets free the handle in C); a non-nodeset result answers
119
132
  # nil and leaves the pointer with the caller — route it through
120
133
  # Searchable.wrap_xpath_result for the scalar faces.
121
- def xpath_result_rows_bulk(result_ptr)
134
+ # Optional (context, adapter): when the consumer passes its
135
+ # contract state, every registered-klass row is born primed and an
136
+ # all-native result extends with BulkIdentityRows — the consumer's
137
+ # per-row wrap prime disappears from the iterate-every-result lane.
138
+ def xpath_result_rows_bulk(result_ptr, mx_context = nil, mx_adapter = nil)
122
139
  Leptris::XML::Native.xpath_result_rows_bulk(
123
140
  self, result_ptr.respond_to?(:address) ? result_ptr.address
124
- : result_ptr)
141
+ : result_ptr,
142
+ mx_context, mx_adapter)
125
143
  end
126
144
 
127
145
  # Builder factories that return NativeNodes (#149): one C call
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.379
4
+ - libleptris v1.9.380
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.380] - 2026-10-22
4
+
5
+ ### Fixed
6
+
7
+ - assess elements against the instance's xsi:type (moxml#433) (xsd)
8
+ - choice alternatives after a failed parenthesized branch were unreachable (dtd)
9
+ - iterative nested-include processing - no C-stack nesting (xinclude)
10
+
11
+
12
+
3
13
  ## [1.9.379] - 2026-10-21
4
14
 
5
15
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.379
6
+ VERSION 1.9.380
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -27,54 +27,62 @@ docs/MISSION-PLAN.md and is updated at every release.
27
27
 
28
28
  ## Gate A — Conformance: zero reds everywhere
29
29
 
30
- ### A1 WHATWG verdict parity: 1394/1555 → 1555/1555
31
- **Measured 2026-10-21 (main after #1800): 1535/1555 verdict,
32
- 1555/1555 tree, zero false positives; floor pinned at 1535.** The
33
- remaining 20 verdict misses: adoption-agency (tests2:10, tests19:98,
34
- tests19:99, tests26:1, tests26:9), menuitem strays (menuitem:10/12),
35
- table/caption singletons (tests9:16, tests10:15, tests16:197,
36
- pending-spec-changes:2, tests20:48), tokenizer singletons (tests10:2,
37
- tests2:18, tests2:58, tests19:41, tests19:82) and two-heads
38
- (tests3:13). The remaining ~270 code-set divergences are prose-dialect
39
- labels from older corpus datasets; the verdict gate is presence-only.
40
- The slices already shipped: initial mode, raw-text EOF, unterminated
41
- close, frameset, fenced walk, InBody EOF allowed-set, in-select,
42
- foster-by-kind, unknown-doctype (#1778-#1791), raw-text strays,
43
- non-html-root, end-tag-too-early, after-body/frameset phases,
44
- start-tag phases, select/table/foreign, need-space-after-doctype
45
- (#1794), misplaced/stray-p, after-html codes, image (#1797, #1800).
46
- Ground every further slice in html5lib's actual source
47
- (~/src/external/html5lib):
48
- - [ ] escaped-state EOF codes (`unexpected-eof-in-text-mode`,
49
- `eof-in-script-in-script`) — the script-data state machine's
50
- EOF classification
51
- - [ ] the after-body EOF phase rule (tests1:34 class; the exact
52
- phaseAtBody.eof semantics)
53
- - [ ] in-body `unexpected-start-tag` family (body/frameset/form
54
- starts; the startTagBody/Forms/Frameset error sites)
55
- - [ ] `unexpected-end-tag` family (p-in-button-scope synthesis,
56
- form pointer, li/dd/dt scope, endTagOther walk)
57
- - [ ] foster-parenting sites (start-tag/character/character-in-table)
58
- - [ ] adoption-agency codes (`adoption-agency-1.3` et al.)
59
- - [ ] in-table voodoo (`*-implies-table-voodoo`, cells, eof-in-table)
60
- - [ ] unknown-doctype / unexpected-char-in-doctype (doctype
61
- tokenization §13.2.5.28+)
62
- - [ ] non-html-root, select-mode codes, heading/edge shapes
30
+ ### A1 WHATWG verdict parity: DONE — 1555/1555
31
+ **Measured 2026-10-21 (PR #1803, tagged v1.9.378): verdict
32
+ 1555/1555, ZERO misses; tree 1555/1555; floor pinned at the full
33
+ corpus count.** The gate is presence-only — every corpus document
34
+ now reports a parse error exactly when html5lib does. The final
35
+ tail closed at the html5lib source sites: adoption-agency-1.3
36
+ (7 cases), menuitem endTagOther pair, tokenizer
37
+ expected-dashes-or-doctype / solidus-in-tag / attributes-in-end-tag,
38
+ AfterAfterFrameset trailing chars, BeforeHead
39
+ end-tag-after-implied-root, BeforeHtml unexpected-end-tag-before-html,
40
+ foreign end-tag mismatch, form-in-table both flavors, </form> table
41
+ voodoo, template foster-parenting-character, two-heads. Earlier
42
+ slices: initial mode, raw-text EOF, unterminated close, frameset,
43
+ fenced walk, InBody EOF allowed-set, in-select, foster-by-kind,
44
+ unknown-doctype (#1778-#1791), raw-text strays, non-html-root,
45
+ end-tag-too-early, after-body/frameset phases, start-tag phases,
46
+ select/table/foreign, need-space-after-doctype (#1794),
47
+ misplaced/stray-p, after-html codes, image (#1797, #1800). The
48
+ ~270 remaining code-set divergences are prose-dialect labels from
49
+ older corpus datasets — reported, not gated.
63
50
 
64
51
  ### A2 QT3 / XQuery 3.1: zero unexplained pins
65
- - [ ] re-pin fresh (the ~41–99 slice figures are dated)
66
- - [ ] every remaining pin gets a disposition file or a fix
52
+ - [x] re-pin fresh (2026-10-22: 1723 pins, 62/62 green, zero reds; 106 sets, 72 pins moved)
53
+ - [x] every remaining pin gets a disposition file or a fix
54
+ (test/xquery/qt3-dispositions/ — 10 files, 1723/1723 dispositioned;
55
+ fixes: fn:codepoint-equal empty-operand propagation, driver
56
+ assert-count/assert-empty, role="." context sources, QT3_REPORT
57
+ inventory channel)
67
58
  - [ ] ctor node-yield family: owned_docs transfer completion
68
59
 
69
60
  ### A3 XSD conformance lane: suite-complete
70
- - [ ] the lane's own queue (769→795 → its ceiling) to zero
61
+ - [x] the lane's own queue to its dispositioned ceiling — **2026-10-22:
62
+ 903 → 989**/1056 (PR #1825; the board's 795 was stale — five slices
63
+ had landed). 9 root-caused classes retired:
64
+ (2026-10-22: 903 → **989**/1056 — 9 root-caused classes retired:
65
+ xsi-attr exemption, empty content models, unprefixed builtins,
66
+ no-tns `##other`, xs:attribute 3.2.3 clauses (+55), g-regional
67
+ timezone, token whitespace, unknown-xs-builtin refs. 67 left,
68
+ every row dispositioned in test/xsd/xsd-dispositions/README.md:
69
+ substitution groups in content models, xsi:type, attribute form
70
+ defaults, identity-constraint checks, derivation-ok attribute
71
+ clauses; 1 oracle bug (bug322411) not chased)
71
72
 
72
73
  ### A4 DTD phase 8 (TODO 91)
73
- - [ ] ENTITY/ENTITIES attribute resolution
74
- - [ ] parameter entities, choice-model backtracking
74
+ - [x] ENTITY/ENTITIES attribute resolution (undeclared entity, parsed
75
+ entity without NDATA, undeclared notation, bad ENTITIES token —
76
+ all falsifiably spec'd; shipped bd21acc6/9d3b3a0e)
77
+ - [x] parameter entities (internal, chained, ATTLIST/ELEMENT bodies,
78
+ SYSTEM PEs via loader, recursion cap, conditional sections) and
79
+ choice-model backtracking — group-alternative failure walker
80
+ fixed so a failed parenthesized alternative still reaches later
81
+ `|` branches (DtdPhase8.GroupAlternativeFailureStillTriesLaterAlternatives)
75
82
 
76
83
  ### A5 XInclude completeness (TODO 92)
77
- - [ ] `parse="xml"` (ownership-transfer splice exists — wire it)
84
+ - [x] `parse="xml"` (ownership-transfer splice + canonical insert face,
85
+ depth-guard resource-error fallback; verified by specs)
78
86
  - [ ] xpointer
79
87
 
80
88
  ### A6 The universality proof (differential corpus)
@@ -90,15 +98,15 @@ Ground every further slice in html5lib's actual source
90
98
  ## Gate B — Performance: every published row ≥1.00x
91
99
 
92
100
  ### B1 build row: ~2.9x → ≤1.0x (the #242 campaign)
93
- **Measured 2026-10-21: 1.86-1.92x** (moxml `sota/04-performance`
94
- branch + leptris 1.9.376.0; harness `wt/460-harness/build_bench.rb`,
95
- 31 interleaved reps: 4.08ms vs Nokogiri 2.20ms for 250 records).
96
- Was 2.95x at the 1.9.363 era; the add_child alias and same-doc fast
97
- path landed since. StackProf attribution: `NativeNode.create_element`
98
- 14.6%, GC mark+sweep 19%, `prime_contract` 7.7%, attach path 45% of
99
- tree; the `invalidate_namespace_cache!` line attribution is a
100
- sampling artifact (deleting both attach invalidations gains ~5%).
101
- Harness: wt/460-harness/build_bench.rb. Decomposition on record.
101
+ **Measured 2026-10-22: ~1.47-1.54x median after NINE merged levers**
102
+ (honest baseline 2.72x; guard retirement −22%, namespace gate,
103
+ nil-ivar priming, children-cache guard, mint direct-dispatch +
104
+ native set_attribute −19% (PR #431), prepare_node inline (PR #432)).
105
+ Harness `wt/460-harness/build_bench.rb`; A/B pairs alternate within
106
+ ONE window (drift ±20%). Rejected within noise: fast_attach,
107
+ native-cache deletion, doc_klass memo, identity-probe deletion,
108
+ add_child alias. Residual: wrapper allocation (owner-level node
109
+ layout).
102
110
  - [ ] per-element object budget: one Ruby object per created node
103
111
  (drop the shell entirely post-create-priming; the mint cache
104
112
  insert policy; the context registration)
@@ -113,8 +121,16 @@ scaffolding. Memo law holds (dropping @children memo = 2x GC).
113
121
  - [ ] publish the per-face table (the honest much-faster story)
114
122
 
115
123
  ### B3 whole-pipeline rows (new, published)
116
- - [ ] parse→query→serialize pipeline row vs nokogiri/libxml2
117
- - [ ] build→transform (XSLT) pipeline row
124
+ **Measured 2026-10-21: parse→query→serialize is GREEN — leptris
125
+ finishes in 0.533-0.573x of Nokogiri's CPU time (~1.8x faster),
126
+ three interleaved-median runs (`wt/460-harness/pipeline_bench.rb`,
127
+ 250 records, 31 reps; same document and query both sides).**
128
+ - [x] parse→query→serialize pipeline row vs nokogiri (green, dated)
129
+ - [x] build→transform (XSLT) pipeline row — **GREEN 2026-10-21:
130
+ build 250 records, XSLT 1.0 transform, serialize — leptris
131
+ finishes in 0.492-0.515x of Nokogiri's CPU time (~2x faster),
132
+ three interleaved-median runs
133
+ (`wt/460-harness/xslt_pipeline_bench.rb`)**
118
134
  - [ ] both to serialbench as the public scoreboard
119
135
 
120
136
  ### B4 durability
@@ -136,19 +152,29 @@ scaffolding. Memo law holds (dropping @children memo = 2x GC).
136
152
  - [ ] C3 reproducible builds everywhere: darwin toolchain pin done
137
153
  (ruby#512) — extend to linux/windows legs; publish a per-
138
154
  release build manifest (engine + binding hashes per platform)
139
- - [ ] C4 security.md: advisory channel, CVE-response process,
140
- dependency-free attestation
155
+ - [x] C4 security.md: advisory channel, CVE-response process,
156
+ dependency-free attestation — **shipped 2026-10-21 (PR #1804,
157
+ v1.9.378)**: GitHub Security Advisories channel, 5-business-day
158
+ acknowledgment target, coordinated disclosure, CVE assignment,
159
+ scope table, hardening inventory tied to the lanes that actually
160
+ run
141
161
  - [ ] C5 the memory story documented: pool arena model, leak gates,
142
162
  the borrowed-slice lifetime contract
143
163
 
144
164
  ## Gate D — Universality: same semantics everywhere
145
165
 
146
- - [ ] D1 leptris-descriptor published (crates.io; feat/294 branch
147
- lands), rides lockstep
166
+ - [x] D1 leptris-descriptor published — **2026-10-22: live on
167
+ crates.io as leptris-descriptor v1.9.379** (lockstep with the
168
+ engine; #1827 fixed the workspace publish to target the
169
+ descriptor package; issue #1829 records the one-time manual
170
+ establishment — crates.io trusted publishing cannot create new
171
+ crates — after which versions publish via the OIDC workflow)
148
172
  - [ ] D2 system packaging: homebrew, conan, deb (apt repo)
149
173
  - [ ] D3 cross-runtime differential CI (= A6, wired)
150
- - [ ] D4 libxml2→libleptris migration guide (docs/guide/) + the
151
- nokogiri-compat surface documented
174
+ - [x] D4 libxml2→libleptris migration guide — **shipped 2026-10-21
175
+ (PR #1810): docs/guide/migrating-from-libxml2.md** (ownership
176
+ model, parse/tree/XPath/serialize/SAX/DTD mappings, deliberate
177
+ differences); nokogiri-compat surface documented in the guide
152
178
 
153
179
  ## Gate E — 1.0 (the seal)
154
180
 
@@ -11,4 +11,4 @@ dependencies = [
11
11
 
12
12
  [[package]]
13
13
  name = "leptris-descriptor"
14
- version = "0.1.0"
14
+ version = "1.9.379"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "leptris-descriptor"
3
- version = "0.1.0"
3
+ version = "1.9.379"
4
4
  edition = "2021"
5
5
  description = "Shared descriptor-plan ABI for the leptris engine family (leptris, yeptris, teptris) — repr(C) mirrors of the tree-shaped descriptor plan records"
6
6
  license = "MIT"