leptris 1.9.24-aarch64-linux → 1.9.26-aarch64-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 +66 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +20 -0
- data/lib/leptris/xml/ffi.rb +94 -29
- data/lib/leptris/xml/node.rb +10 -4
- data/lib/leptris/xml/node_set.rb +13 -14
- data/lib/leptris/xml/pull.rb +65 -10
- data/lib/libleptris.so +0 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ff1e0252cab3e0bf0f626b2561c127325c2540332d1049e0eaec864f5065d29
|
|
4
|
+
data.tar.gz: 2bc04e7940829b977148af31a56e8044496c1138773858a25e69c0d216b81fee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 902dbd67e428e7efd945017cc2a72e6d80c79250b19eabaeeceeab711c7d00669dbaecf3696778bce1780fe1f613042201eb12042370a767b2705ebc392d06b0
|
|
7
|
+
data.tar.gz: 5eb92eb2921f786ad97dbebe5131e508d7ae16d205f073fed6010ea91cdbefd0db933457ed0d628f18b54e557ea8c8c2a67f1df4c9bf5c9c3f511700ec53b01d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,72 @@ 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.26] - 2026-08-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **libleptris 1.9.7 lockstep** (upstream #602/#604): Rakefile pin
|
|
13
|
+
bumped; symbol audit 247/247 with the five new functions.
|
|
14
|
+
- **`Pull::Parser#each_batch(max = 256)`** — the #589 batch
|
|
15
|
+
transport: ONE C call stages up to max events (scratch-staged,
|
|
16
|
+
layout-offset drain, no per-event dispatch). Streaming big.xml
|
|
17
|
+
(250k events): 408 ms -> **210 ms per parse (-48%)**; the cursor
|
|
18
|
+
path also drops to 347 ms (Events are now constructed
|
|
19
|
+
positionally — the keyword-init Struct cost ~220 ns/event; a
|
|
20
|
+
minor API change: `Pull::Event` no longer takes keyword args,
|
|
21
|
+
readers unchanged). Per the engine's attr-mirror protocol,
|
|
22
|
+
attributes are captured for each batch's LAST start_element —
|
|
23
|
+
use `#each` when every start's attrs must be present. Attr
|
|
24
|
+
capture on both paths is now one count query + one flat copy
|
|
25
|
+
(`leptris_pull_attrs`) instead of 2N per-index dispatches.
|
|
26
|
+
- **`Document#node` / `Document#children`** (#580, our upstream
|
|
27
|
+
ask): document-level PIs/comments are tree children behind a
|
|
28
|
+
stable singleton navigation head — `children` is
|
|
29
|
+
[prolog…, root, epilog…] in document order (the libxml2/Nokogiri
|
|
30
|
+
model), and `/comment()` / `//processing-instruction()` see the
|
|
31
|
+
document-level nodes. The dedicated memoized
|
|
32
|
+
`#processing_instructions` / `#comments` readers are unchanged.
|
|
33
|
+
- **`NodeSet#xpath` through the union entry** (`leptris_xpath_
|
|
34
|
+
eval_nodeset`, #589): one C call for N contexts — results
|
|
35
|
+
de-duplicated and document-ordered, which the per-member Ruby
|
|
36
|
+
loop it replaces could not guarantee.
|
|
37
|
+
|
|
38
|
+
### Meta
|
|
39
|
+
|
|
40
|
+
- Engine swap 1.9.4 -> 1.9.7 regression battery (interleaved
|
|
41
|
+
best-of-3): cold walk 4,637 vs 4,652 µs, DOM parse 72 vs 73 µs,
|
|
42
|
+
SAX text-only 22.7 vs 22.6 ms — parity across the board. The
|
|
43
|
+
v1.9.6 XPath fixes (`/descendant::` root inclusion, $var head
|
|
44
|
+
dropping) are covered by the existing suite plus the new specs.
|
|
45
|
+
|
|
46
|
+
## [1.9.25] - 2026-08-28
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **Cold full-tree walk regression (1.9.1 → 1.9.24, ~1.4-1.5x)
|
|
51
|
+
recovered to parity**: the batch-fetch era (1.9.11-1.9.17) bought
|
|
52
|
+
its warm-path wins with per-call machinery the cold branch paid
|
|
53
|
+
on every children() — reproduced, bisected across every published
|
|
54
|
+
release, and profiled end to end this round. Raw cold walk of a
|
|
55
|
+
~4,200-node catalog (interleaved best-of-5): 1.9.24 5,441 µs ->
|
|
56
|
+
**4,519 µs (-17%)**, vs 1.9.1's 4,369 µs = parity. Through moxml
|
|
57
|
+
the same shape improves 20.1 ms -> 16.5 ms (-18%).
|
|
58
|
+
- `FFI.fetch_children` (and the XPath result batch) now use
|
|
59
|
+
thread-local scratch buffers that grow to the largest family
|
|
60
|
+
seen and are reused — no MemoryPointer allocation/free per
|
|
61
|
+
call, and materially less GC. The count query is paid only when
|
|
62
|
+
the buffer fills exactly (possible truncation): a family that
|
|
63
|
+
fits costs ONE dispatch, not two.
|
|
64
|
+
- `Node.wrap`'s miss path resolves the wrapper cache and pointer
|
|
65
|
+
address once instead of twice — a cold walk wraps every node
|
|
66
|
+
exactly once, so the cache is 100% misses and every redundant
|
|
67
|
+
resolution was pure cost. Identity semantics unchanged.
|
|
68
|
+
- Kept: the per-field version-stamped memo stores on writable
|
|
69
|
+
documents (ADR-0003's writable extension — the residual ~3% vs
|
|
70
|
+
1.9.1 is that feature working) and the batch architecture
|
|
71
|
+
itself (never more FFI dispatches than the linked-list walk,
|
|
72
|
+
and 2 vs 701 for the 700-child root).
|
|
73
|
+
|
|
8
74
|
## [1.9.24] - 2026-08-27
|
|
9
75
|
|
|
10
76
|
### Changed
|
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.7"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -151,6 +151,26 @@ class Leptris::XML::Document
|
|
|
151
151
|
Leptris::XML::Node.wrap(ptr, self)
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
# The document node — navigation head over the whole tree chain
|
|
155
|
+
# [prolog comments/PIs, root element, epilog comments/PIs] in
|
|
156
|
+
# document order (libleptris 1.9.7, upstream #580: the libxml2
|
|
157
|
+
# model; XPath /comment() and //processing-instruction() see the
|
|
158
|
+
# document-level nodes). A stable, document-owned singleton —
|
|
159
|
+
# Node.wrap's cache keeps the returned wrapper identical.
|
|
160
|
+
def node
|
|
161
|
+
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
162
|
+
@node ||= Leptris::XML::Node.wrap(
|
|
163
|
+
Leptris::XML::FFI.leptris_document_node(@c_ptr), self)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The document's children, via the document node: prolog
|
|
167
|
+
# comments/PIs, the root element, epilog comments/PIs, in
|
|
168
|
+
# document order (Nokogiri-parity shape).
|
|
169
|
+
def children
|
|
170
|
+
doc_node = node
|
|
171
|
+
doc_node ? doc_node.children : Leptris::XML::NodeSet.new(self, [])
|
|
172
|
+
end
|
|
173
|
+
|
|
154
174
|
# Attach +element+ as the document's root element. The element must
|
|
155
175
|
# have been created against this document and must not already have
|
|
156
176
|
# a parent. Any previous root is left detached (still owned by the
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -143,6 +143,11 @@ module Leptris
|
|
|
143
143
|
[:leptris_document, :leptris_element], :leptris_status
|
|
144
144
|
attach_function :leptris_document_root,
|
|
145
145
|
[:leptris_document], :leptris_element
|
|
146
|
+
# The document node — navigation head over the whole tree
|
|
147
|
+
# chain [prolog comments/PIs, root, epilog...] (libleptris
|
|
148
|
+
# 1.9.7, upstream #580; the libxml2 model).
|
|
149
|
+
attach_function :leptris_document_node,
|
|
150
|
+
[:leptris_document], :leptris_node_ref
|
|
146
151
|
# Document-level PIs (v1.6.0): not tree nodes — enumerate via
|
|
147
152
|
# these accessors only.
|
|
148
153
|
attach_function :leptris_document_pi_count,
|
|
@@ -430,6 +435,14 @@ module Leptris
|
|
|
430
435
|
[:leptris_document, :leptris_element, :string], :leptris_xpath_result
|
|
431
436
|
attach_function :leptris_xpath_eval_with_vars,
|
|
432
437
|
[:leptris_document, :string, :leptris_xpath_var_set], :leptris_xpath_result
|
|
438
|
+
# Union semantics (NodeSet#xpath): N contexts, ONE call — the
|
|
439
|
+
# engine merges the per-context results into a de-duplicated,
|
|
440
|
+
# document-ordered nodeset (libleptris 1.9.7, upstream #589).
|
|
441
|
+
attach_function :leptris_xpath_eval_nodeset,
|
|
442
|
+
[:leptris_document, :pointer, :size_t, :string], :leptris_xpath_result
|
|
443
|
+
attach_function :leptris_xpath_compiled_eval_nodeset,
|
|
444
|
+
[:leptris_document, :pointer, :size_t, :leptris_xpath_compiled],
|
|
445
|
+
:leptris_xpath_result
|
|
433
446
|
attach_function :leptris_xpath_eval_with_vars_context,
|
|
434
447
|
[:leptris_document, :leptris_element, :string, :leptris_xpath_var_set],
|
|
435
448
|
:leptris_xpath_result
|
|
@@ -552,12 +565,16 @@ module Leptris
|
|
|
552
565
|
[:string], :leptris_pull_parser
|
|
553
566
|
attach_function :leptris_pull_next,
|
|
554
567
|
[:leptris_pull_parser], :pointer
|
|
568
|
+
attach_function :leptris_pull_next_batch,
|
|
569
|
+
[:leptris_pull_parser, :pointer, :size_t], :size_t
|
|
555
570
|
attach_function :leptris_pull_attr_count,
|
|
556
571
|
[:leptris_pull_parser], :size_t
|
|
557
572
|
attach_function :leptris_pull_attr_name,
|
|
558
573
|
[:leptris_pull_parser, :size_t], :string
|
|
559
574
|
attach_function :leptris_pull_attr_value,
|
|
560
575
|
[:leptris_pull_parser, :size_t], :string
|
|
576
|
+
attach_function :leptris_pull_attrs,
|
|
577
|
+
[:leptris_pull_parser, :pointer, :size_t], :size_t
|
|
561
578
|
attach_function :leptris_pull_free,
|
|
562
579
|
[:leptris_pull_parser], :void
|
|
563
580
|
|
|
@@ -817,16 +834,33 @@ module Leptris
|
|
|
817
834
|
end
|
|
818
835
|
end
|
|
819
836
|
|
|
820
|
-
# All-kind child handle batch (libleptris 1.7.0)
|
|
821
|
-
#
|
|
822
|
-
#
|
|
837
|
+
# All-kind child handle batch (libleptris 1.7.0). Opportunistic
|
|
838
|
+
# single dispatch: the C call copies up to the capacity handed
|
|
839
|
+
# it and returns what fit, so the count query is paid only when
|
|
840
|
+
# the buffer fills exactly (a possible truncation) — families
|
|
841
|
+
# that fit the scratch buffer cost ONE dispatch, not two.
|
|
842
|
+
FETCH_INITIAL = 32
|
|
843
|
+
private_constant :FETCH_INITIAL
|
|
844
|
+
|
|
823
845
|
def self.fetch_children(c_ptr)
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
846
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
847
|
+
buffer = scratch[:pointers]
|
|
848
|
+
if buffer.nil?
|
|
849
|
+
buffer = scratch[:pointers] =
|
|
850
|
+
::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL)
|
|
851
|
+
end
|
|
852
|
+
cap = buffer.size / PTR_BYTES
|
|
853
|
+
copied = leptris_node_children(c_ptr, buffer, cap)
|
|
854
|
+
if copied == cap
|
|
855
|
+
total = leptris_node_children(c_ptr, nil, 0)
|
|
856
|
+
if total > cap
|
|
857
|
+
buffer.free
|
|
858
|
+
buffer = scratch[:pointers] =
|
|
859
|
+
::FFI::MemoryPointer.new(:pointer, total)
|
|
860
|
+
copied = leptris_node_children(c_ptr, buffer, total)
|
|
861
|
+
end
|
|
829
862
|
end
|
|
863
|
+
buffer.get_array_of_pointer(0, copied)
|
|
830
864
|
end
|
|
831
865
|
|
|
832
866
|
# XPath result-set batch (leptris_xpath_result_get_nodes_ex):
|
|
@@ -840,18 +874,13 @@ module Leptris
|
|
|
840
874
|
|
|
841
875
|
def self.fetch_result_nodes(result_ptr, count)
|
|
842
876
|
return [[], nil] if count.zero?
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
[buffer.get_array_of_pointer(0, copied), hints]
|
|
851
|
-
ensure
|
|
852
|
-
kinds.free
|
|
853
|
-
end
|
|
854
|
-
end
|
|
877
|
+
buffer = scratch_pointers(count)
|
|
878
|
+
kinds = scratch_ints(count)
|
|
879
|
+
copied = leptris_xpath_result_get_nodes_ex(
|
|
880
|
+
result_ptr, buffer, kinds, count)
|
|
881
|
+
hints = kinds.get_array_of_int(0, copied)
|
|
882
|
+
.map { |k| XPATH_KIND_HINT[k] }
|
|
883
|
+
[buffer.get_array_of_pointer(0, copied), hints]
|
|
855
884
|
end
|
|
856
885
|
|
|
857
886
|
# Fragment parse with the status out-param read: returns
|
|
@@ -863,16 +892,52 @@ module Leptris
|
|
|
863
892
|
[raw, status.read_int]
|
|
864
893
|
end
|
|
865
894
|
|
|
866
|
-
#
|
|
867
|
-
#
|
|
868
|
-
#
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
895
|
+
# Thread-local scratch buffers for batch handle fetches (round
|
|
896
|
+
# XX): the per-call MemoryPointer new/free was pure cold-path
|
|
897
|
+
# overhead — two allocations and a GC visit per children() /
|
|
898
|
+
# result-set batch, ~15% of a cold full-tree walk between
|
|
899
|
+
# 1.9.1 and 1.9.24. The buffers grow to the largest request
|
|
900
|
+
# seen (bounded by the widest element or result set) and are
|
|
901
|
+
# reused. Safe: no Ruby reentrancy during the C call, and
|
|
902
|
+
# per-thread storage keeps concurrent fetches independent.
|
|
903
|
+
# Never freed — a few KB pinned per thread.
|
|
904
|
+
PTR_BYTES = ::FFI.type_size(:pointer)
|
|
905
|
+
private_constant :PTR_BYTES
|
|
906
|
+
INT_BYTES = ::FFI.type_size(:int)
|
|
907
|
+
private_constant :INT_BYTES
|
|
908
|
+
|
|
909
|
+
def self.scratch_pointers(count)
|
|
910
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
911
|
+
ptr = scratch[:pointers]
|
|
912
|
+
if ptr.nil? || ptr.size / PTR_BYTES < count
|
|
913
|
+
ptr&.free
|
|
914
|
+
ptr = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, count)
|
|
915
|
+
end
|
|
916
|
+
ptr
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
def self.scratch_ints(count)
|
|
920
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
921
|
+
ptr = scratch[:ints]
|
|
922
|
+
if ptr.nil? || ptr.size / INT_BYTES < count
|
|
923
|
+
ptr&.free
|
|
924
|
+
ptr = scratch[:ints] = ::FFI::MemoryPointer.new(:int, count)
|
|
925
|
+
end
|
|
926
|
+
ptr
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Pull-event staging block for leptris_pull_next_batch (the
|
|
930
|
+
# #589 bulk transport): LeptrisPullEvent records, stride from
|
|
931
|
+
# the struct layout that mirrors the ABI.
|
|
932
|
+
def self.scratch_events(count)
|
|
933
|
+
need = count * PullEventStruct.size
|
|
934
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
935
|
+
ptr = scratch[:pull_events]
|
|
936
|
+
if ptr.nil? || ptr.size < need
|
|
937
|
+
ptr&.free
|
|
938
|
+
ptr = scratch[:pull_events] = ::FFI::MemoryPointer.new(need)
|
|
875
939
|
end
|
|
940
|
+
ptr
|
|
876
941
|
end
|
|
877
942
|
end
|
|
878
943
|
end
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -19,9 +19,15 @@ class Leptris::XML::Node
|
|
|
19
19
|
# Per-document weak-ref cache. Returns the existing wrapper when the
|
|
20
20
|
# same c_ptr is wrapped twice (common in children/sibling walks,
|
|
21
21
|
# repeated xpath queries, traverse-then-access patterns). The cache
|
|
22
|
-
# dies with the document so no stale entries.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# dies with the document so no stale entries. The miss path
|
|
23
|
+
# resolves the cache and address once — a cold walk wraps every
|
|
24
|
+
# node exactly once and pays both only on the store.
|
|
25
|
+
if document
|
|
26
|
+
cache = document.wrapper_cache
|
|
27
|
+
address = c_ptr.address
|
|
28
|
+
if (cached = cache[address])
|
|
29
|
+
return cached
|
|
30
|
+
end
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
node_type ||= Leptris::XML::FFI.leptris_node_get_type(c_ptr)
|
|
@@ -41,7 +47,7 @@ class Leptris::XML::Node
|
|
|
41
47
|
new(c_ptr, document, parent: parent, node_type: node_type)
|
|
42
48
|
end
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
cache[address] = node if document
|
|
45
51
|
node
|
|
46
52
|
end
|
|
47
53
|
|
data/lib/leptris/xml/node_set.rb
CHANGED
|
@@ -127,24 +127,23 @@ class Leptris::XML::NodeSet
|
|
|
127
127
|
end
|
|
128
128
|
alias_method :text, :inner_text
|
|
129
129
|
|
|
130
|
-
# Union semantics:
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
130
|
+
# Union semantics: ONE engine call evaluates the expression
|
|
131
|
+
# against every element member and merges the results into a
|
|
132
|
+
# de-duplicated, document-ordered nodeset (libleptris 1.9.7,
|
|
133
|
+
# upstream #589 — the per-member Ruby loop this replaces could
|
|
134
|
+
# neither dedup nor order).
|
|
134
135
|
def xpath(*paths)
|
|
135
136
|
handler, _ns, _vars = parse_search_args(paths)
|
|
136
137
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
137
138
|
expr = paths.join(" | ")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
end
|
|
147
|
-
Leptris::XML::NodeSet.new(@document, accumulated)
|
|
139
|
+
contexts = to_a.select { |node| node.is_a?(Leptris::XML::Element) }
|
|
140
|
+
return Leptris::XML::NodeSet.new(@document, []) if contexts.empty?
|
|
141
|
+
buffer = Leptris::XML::FFI.scratch_pointers(contexts.size)
|
|
142
|
+
buffer.write_array_of_pointer(contexts.map(&:c_ptr))
|
|
143
|
+
result_ptr = Leptris::XML::FFI.leptris_xpath_eval_nodeset(
|
|
144
|
+
@document.c_ptr, buffer, contexts.size, expr)
|
|
145
|
+
return Leptris::XML::NodeSet.new(@document, []) if result_ptr.null?
|
|
146
|
+
Leptris::XML::NodeSet.from_result(@document, result_ptr)
|
|
148
147
|
end
|
|
149
148
|
|
|
150
149
|
def inspect
|
data/lib/leptris/xml/pull.rb
CHANGED
|
@@ -14,7 +14,10 @@ require "ffi"
|
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
16
|
module Leptris::XML::Pull
|
|
17
|
-
|
|
17
|
+
# Constructed positionally by the parser: the keyword-init
|
|
18
|
+
# variant cost ~220ns/event in batch drains (round XXI). Readers
|
|
19
|
+
# (event.type, event.attrs, ...) are unchanged.
|
|
20
|
+
Event = Struct.new(:type, :name, :text, :attrs)
|
|
18
21
|
|
|
19
22
|
TYPES = {
|
|
20
23
|
Leptris::XML::FFI::PULL_START_ELEMENT => :start_element,
|
|
@@ -60,6 +63,49 @@ module Leptris::XML::Pull
|
|
|
60
63
|
self
|
|
61
64
|
end
|
|
62
65
|
|
|
66
|
+
# Bulk-delivers events (libleptris 1.9.7, upstream #589): ONE C
|
|
67
|
+
# call stages up to +max_count+ events — the per-event dispatch
|
|
68
|
+
# that made streaming ~145x slower than a DOM parse collapses to
|
|
69
|
+
# amortized noise. Per the engine's protocol, attributes are
|
|
70
|
+
# captured only for the LAST start_element of each batch
|
|
71
|
+
# (earlier starts in the same batch carry nil attrs — segment
|
|
72
|
+
# boundaries fall where they fall); use #each when every
|
|
73
|
+
# start's attrs must be captured.
|
|
74
|
+
def each_batch(max_count = 256)
|
|
75
|
+
return enum_for(:each_batch, max_count) unless block_given?
|
|
76
|
+
stride = Leptris::XML::FFI::PullEventStruct.size
|
|
77
|
+
type_off = 0
|
|
78
|
+
name_off = Leptris::XML::FFI::PullEventStruct.offset_of(:name)
|
|
79
|
+
text_off = Leptris::XML::FFI::PullEventStruct.offset_of(:text)
|
|
80
|
+
buffer = Leptris::XML::FFI.scratch_events(max_count)
|
|
81
|
+
while (n = Leptris::XML::FFI.leptris_pull_next_batch(
|
|
82
|
+
@handle, buffer, max_count)) > 0
|
|
83
|
+
events = Array.new(n) do |i|
|
|
84
|
+
base = i * stride
|
|
85
|
+
name_ptr = buffer.get_pointer(base + name_off)
|
|
86
|
+
text_ptr = buffer.get_pointer(base + text_off)
|
|
87
|
+
Event.new(
|
|
88
|
+
TYPES[buffer.get_int(base + type_off)],
|
|
89
|
+
name_ptr.null? ? nil :
|
|
90
|
+
name_ptr.read_string.force_encoding(Encoding::UTF_8),
|
|
91
|
+
text_ptr.null? ? nil :
|
|
92
|
+
text_ptr.read_string.force_encoding(Encoding::UTF_8),
|
|
93
|
+
nil
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
# The attr mirror holds the batch's most recent start.
|
|
97
|
+
last_start = nil
|
|
98
|
+
events.each_with_index do |event, i|
|
|
99
|
+
last_start = i if event.type == :start_element
|
|
100
|
+
end
|
|
101
|
+
events[last_start].attrs = capture_attrs if last_start
|
|
102
|
+
events.each { |event| yield event }
|
|
103
|
+
tail = events.last.type
|
|
104
|
+
break if tail == :end_document || tail == :error
|
|
105
|
+
end
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
63
109
|
# Hot-loop offsets: derived from PullEventStruct's layout (the
|
|
64
110
|
# struct stays the single source of truth for the ABI), read via
|
|
65
111
|
# get_int/get_pointer so the per-event struct-wrapper allocation
|
|
@@ -80,23 +126,32 @@ module Leptris::XML::Pull
|
|
|
80
126
|
text_ptr = raw.get_pointer(TEXT_OFFSET)
|
|
81
127
|
attrs = type == :start_element ? capture_attrs : nil
|
|
82
128
|
Event.new(
|
|
83
|
-
type
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
attrs
|
|
129
|
+
type,
|
|
130
|
+
name_ptr.null? ? nil : name_ptr.read_string.force_encoding(Encoding::UTF_8),
|
|
131
|
+
text_ptr.null? ? nil : text_ptr.read_string.force_encoding(Encoding::UTF_8),
|
|
132
|
+
attrs
|
|
87
133
|
)
|
|
88
134
|
end
|
|
89
135
|
|
|
90
136
|
private
|
|
91
137
|
|
|
138
|
+
# One count-only query plus one flat copy (leptris_pull_attrs,
|
|
139
|
+
# 1.9.7) — the char** wire format the SAX callbacks already use —
|
|
140
|
+
# replaces the 2N per-index dispatches.
|
|
92
141
|
def capture_attrs
|
|
93
|
-
|
|
94
|
-
return nil if
|
|
142
|
+
total = Leptris::XML::FFI.leptris_pull_attrs(@handle, nil, 0)
|
|
143
|
+
return nil if total.zero?
|
|
144
|
+
buffer = Leptris::XML::FFI.scratch_pointers(2 * total)
|
|
145
|
+
copied = Leptris::XML::FFI.leptris_pull_attrs(@handle, buffer, total)
|
|
146
|
+
ptr_size = ::FFI.type_size(:pointer)
|
|
95
147
|
hash = {}
|
|
96
148
|
i = 0
|
|
97
|
-
while i <
|
|
98
|
-
|
|
99
|
-
|
|
149
|
+
while i < copied
|
|
150
|
+
name = buffer.get_pointer(2 * i * ptr_size).read_string
|
|
151
|
+
.force_encoding(Encoding::UTF_8)
|
|
152
|
+
value = buffer.get_pointer((2 * i + 1) * ptr_size).read_string
|
|
153
|
+
.force_encoding(Encoding::UTF_8)
|
|
154
|
+
hash[name] = value
|
|
100
155
|
i += 1
|
|
101
156
|
end
|
|
102
157
|
hash
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.26
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|