leptris 1.9.199.0-arm-linux → 1.9.201.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1246f7d20c2b80ba4502e6185f239c6a614b8d3d7587359f4fa91da3a0d40613
4
- data.tar.gz: 31cfd799241888660103004201d7cbe1dd24feea593db85373fb62b32118bdf9
3
+ metadata.gz: 6841a0108841399f01dae30bb346207c18df5135b11cc9b43374ecdceea409bd
4
+ data.tar.gz: 70e9d1da5932b808364f840b12e4be6c7380e82d267538899ab17ce840e6dfdc
5
5
  SHA512:
6
- metadata.gz: 6dc24866901b0eb2f1823e54f99a5b58c05966b0b9b54c8eacdb843e9055b0541be5181ee46039cb01390c7b9ef93ff06223bce1232414a6d250fbf8eea7d7b9
7
- data.tar.gz: 8172f2daa4d5d3781f8ed402bfea1e1106f252d9a9be3b9116833395c3be08722b700cec518a10ab036f77c9d888e6f32ca72b5d5b1ba8b4a0eb4e78f4790216
6
+ metadata.gz: 6aceebffe370f004b1336db46f86460466fa4806ae3fef4a591368867a4d834717ab6602e20894b79c6f1d572ae6b505afa9410ab531e683a38f0beec97964a2
7
+ data.tar.gz: 24d68049747b967006b1dbf55cc389bc8b5f894d46c0a14aeff284afc14bf4131a65b8be731c99da953c2affdba686cc504f6c988a2139d3b9b66e29fb743fbf
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.201.0] - 2026-09-19
9
+
10
+ ### Changed — libleptris 1.9.199 → 1.9.201 (lockstep)
11
+
12
+ - **SAX pull ERROR events carry their text length** (1.9.200) —
13
+ the last event path without it; the binding's pull reader now
14
+ reads text **length-driven** (`text_len`, width-dispatched)
15
+ instead of NUL-terminated, per the engine's event contract.
16
+ Spec-pinned: a pull ERROR event's message arrives intact.
17
+ - `leptris diff --summary/--json` CLI modes (1.9.200) — engine
18
+ surface unchanged (CLI-only); the binding's diff accessors were
19
+ already complete.
20
+ - No public-symbol changes: audit 323/323. 730/0 both modes.
21
+
8
22
  ## [1.9.199.0] - 2026-09-19
9
23
 
10
24
  ### Changed — libleptris 1.9.197 → 1.9.199 (lockstep; 1.9.198/199
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.199"
11
+ LIBLEPTRIS_VERSION = "1.9.201"
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
@@ -1941,6 +1941,122 @@ static VALUE nf_snapshot_rows(VALUE self, VALUE document, VALUE addr)
1941
1941
  return out;
1942
1942
  }
1943
1943
 
1944
+ /* ---- Struct plan executor (moxml "miles faster" round) ------------
1945
+ * The consumer's shape compiles to spec: name (interned String) ->
1946
+ * [Struct class, attrs Hash (name -> slot Symbol), text slot Symbol
1947
+ * or nil, children slot Symbol or nil]. C walks the subtree
1948
+ * pre-order, mints typed structs directly (rb_struct_new +
1949
+ * rb_struct_aset), and attaches each to the nearest OPEN frame —
1950
+ * exactly Moxml::Plan's semantics: an unmatched element is a
1951
+ * barrier (its matched descendants surface at top level). Zero
1952
+ * Ruby frames and zero intermediate rows per element. */
1953
+ static void plan_walk(void *node, int depth, VALUE spec, VALUE stack,
1954
+ VALUE roots)
1955
+ {
1956
+ if (f_node_type(node) == WS_NODE_ELEMENT) {
1957
+ const char *name_c = f_elem_name(node);
1958
+ VALUE entry = name_c ? rb_hash_aref(spec,
1959
+ rb_enc_interned_str(name_c, strlen(name_c),
1960
+ rb_utf8_encoding()))
1961
+ : Qnil;
1962
+
1963
+ /* Complete deeper frames: pre-order means any open frame at
1964
+ * depth >= this element's depth belongs to an earlier
1965
+ * subtree that ended before this row. */
1966
+ while (RARRAY_LEN(stack) >= 2 &&
1967
+ NUM2INT(rb_ary_entry(stack, RARRAY_LEN(stack) - 2)) >=
1968
+ depth) {
1969
+ rb_ary_pop(stack);
1970
+ rb_ary_pop(stack);
1971
+ }
1972
+
1973
+ if (!NIL_P(entry)) {
1974
+ /* volatile: keep live VALUEs on the C stack so the
1975
+ * conservative scanner sees them on every ABI (windows
1976
+ * x64 keeps some only in callee-saved registers). */
1977
+ volatile VALUE klass = rb_ary_entry(entry, 0);
1978
+ volatile VALUE attrs_spec = rb_ary_entry(entry, 1);
1979
+ volatile VALUE text_slot = rb_ary_entry(entry, 2);
1980
+ volatile VALUE children_slot = rb_ary_entry(entry, 3);
1981
+ VALUE value = rb_struct_new(klass);
1982
+
1983
+ for (void *a = f_attr_first(node); a; a = f_attr_next(a)) {
1984
+ const char *an = f_attr_name(a);
1985
+ VALUE slot = an ? rb_hash_aref(attrs_spec,
1986
+ rb_enc_interned_str(an, strlen(an),
1987
+ rb_utf8_encoding()))
1988
+ : Qnil;
1989
+ if (!NIL_P(slot)) {
1990
+ const char *av = f_attr_value(node, a);
1991
+ rb_struct_aset(value, slot,
1992
+ av ? rb_utf8_str_new_cstr(av) : Qnil);
1993
+ }
1994
+ }
1995
+ if (!NIL_P(text_slot)) {
1996
+ void *c;
1997
+ for (c = f_first_child(node); c; c = f_next_sibling(c)) {
1998
+ if (f_node_type(c) == WS_NODE_TEXT) {
1999
+ const char *t = f_text_content(c);
2000
+ rb_struct_aset(value, text_slot,
2001
+ t ? rb_utf8_str_new_cstr(t) : Qnil);
2002
+ break;
2003
+ }
2004
+ }
2005
+ }
2006
+
2007
+ /* attach: nearest open frame carrying a children array;
2008
+ * barriers (unmatched ancestors) and the root surface
2009
+ * the value at top level — Moxml::Plan's contract. */
2010
+ if (RARRAY_LEN(stack) >= 2 &&
2011
+ rb_ary_entry(stack, RARRAY_LEN(stack) - 1) != Qnil) {
2012
+ rb_ary_push(rb_ary_entry(stack, RARRAY_LEN(stack) - 1),
2013
+ value);
2014
+ } else {
2015
+ rb_ary_push(roots, value);
2016
+ }
2017
+ RB_GC_GUARD(value);
2018
+ RB_GC_GUARD(entry);
2019
+
2020
+ if (!NIL_P(children_slot)) {
2021
+ VALUE children = rb_ary_new();
2022
+ rb_struct_aset(value, children_slot, children);
2023
+ rb_ary_push(stack, INT2NUM(depth));
2024
+ rb_ary_push(stack, children);
2025
+ }
2026
+ } else {
2027
+ /* unmatched: barrier frame — descendants must not
2028
+ * attach through it (Moxml::Plan parity). */
2029
+ rb_ary_push(stack, INT2NUM(depth));
2030
+ rb_ary_push(stack, Qnil);
2031
+ }
2032
+
2033
+ void *c;
2034
+ for (c = f_first_child(node); c; c = f_next_sibling(c)) {
2035
+ plan_walk(c, depth + 1, spec, stack, roots);
2036
+ }
2037
+ } else if (f_node_type(node) == WS_NODE_DOCTYPE ||
2038
+ f_node_type(node) == 9) {
2039
+ void *dc = f_first_child(node);
2040
+ while (dc) {
2041
+ plan_walk(dc, depth, spec, stack, roots);
2042
+ dc = f_next_sibling(dc);
2043
+ }
2044
+ }
2045
+ }
2046
+
2047
+ static VALUE nf_plan_structs(VALUE self, VALUE document, VALUE addr,
2048
+ VALUE spec)
2049
+ {
2050
+ (void)self;
2051
+ (void)document;
2052
+ VALUE roots = rb_ary_new();
2053
+ VALUE stack = rb_ary_new();
2054
+ plan_walk((void *)(uintptr_t)NUM2ULL(addr), 0, spec, stack, roots);
2055
+ RB_GC_GUARD(spec);
2056
+ RB_GC_GUARD(stack);
2057
+ return roots;
2058
+ }
2059
+
1944
2060
  /* ---- inner_html in one C pass (TODO.perf/18) --------------------
1945
2061
  * Serializes the receiver's children into one growable buffer:
1946
2062
  * elements via leptris_element_serialize_into (the same opts the
@@ -2490,6 +2606,8 @@ void Init_native(void)
2490
2606
  nf_snapshot_subtree, 2);
2491
2607
  rb_define_module_function(m_native, "snapshot_rows",
2492
2608
  nf_snapshot_rows, 2);
2609
+ rb_define_module_function(m_native, "plan_structs",
2610
+ nf_plan_structs, 3);
2493
2611
  rb_define_module_function(m_native, "doc_handle_attach",
2494
2612
  nf_doc_handle_attach, 1);
2495
2613
  rb_define_module_function(m_native, "doc_handle_release",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.199.0"
4
+ VERSION = "1.9.201.0"
5
5
  end
Binary file
@@ -136,7 +136,13 @@ module Leptris::XML::Pull
136
136
  # disappears from streaming loops.
137
137
  NAME_OFFSET = Leptris::XML::FFI::PullEventStruct.offset_of(:name)
138
138
  TEXT_OFFSET = Leptris::XML::FFI::PullEventStruct.offset_of(:text)
139
- private_constant :NAME_OFFSET, :TEXT_OFFSET
139
+ # Length-driven text reads (1.9.200's ERROR-event text_len fix
140
+ # closed the last zero-length path): NUL-terminated read_string
141
+ # over-reads unterminated content and truncates on embedded
142
+ # NULs.
143
+ TEXT_LEN_OFFSET = Leptris::XML::FFI::PullEventStruct.offset_of(:text_len)
144
+ SIZE_T_64 = FFI.type_size(:size_t) == 8
145
+ private_constant :NAME_OFFSET, :TEXT_OFFSET, :TEXT_LEN_OFFSET, :SIZE_T_64
140
146
 
141
147
  # Advances the cursor and returns the next Event, or nil after the
142
148
  # end of the document. Attribute values are captured during
@@ -149,8 +155,12 @@ module Leptris::XML::Pull
149
155
  name_ptr = raw.get_pointer(NAME_OFFSET)
150
156
  text_ptr = raw.get_pointer(TEXT_OFFSET)
151
157
  attrs = type == :start_element ? capture_attrs : nil
158
+ # size_t is pointer-width: dispatch the read once (Pointer has
159
+ # no width-generic accessor) — no per-event struct wrapper.
160
+ text_len = SIZE_T_64 ? raw.get_uint64(TEXT_LEN_OFFSET)
161
+ : raw.get_uint32(TEXT_LEN_OFFSET)
152
162
  text = text_ptr.null? ? nil :
153
- text_ptr.read_string.force_encoding(Encoding::UTF_8)
163
+ text_ptr.read_string(text_len).force_encoding(Encoding::UTF_8)
154
164
  text = Leptris::XML::FFI.read_pi_data(text) if type == :pi
155
165
  Event.new(
156
166
  type,
data/lib/libleptris.so CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.199.0
4
+ version: 1.9.201.0
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Ribose Inc.