leptris 1.9.26-aarch64-linux → 1.9.28-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 +46 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +26 -0
- data/lib/leptris/xml/node.rb +31 -4
- data/lib/leptris/xml/sax/parser.rb +15 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc27ce25308956b6f5cbd275686b8872f7082f6c3cea568d127b5655a315fb94
|
|
4
|
+
data.tar.gz: 6f06edf1df2cf34dfcd40f749d5e5ddc481deec21197e45b0d9e38f6bddf6333
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8c27e5ce7f4b67a3940e88e369934ed03ad6233f2387c3924f64d0e7c3486968a7a4c3431e6f9d4e23cd11affb657e002537a038f1a0248af7c689188725e1d
|
|
7
|
+
data.tar.gz: c7ed1e5a92a463ccad6908a039b359cfb640e197717de04865e5e59f3fbcbba6bd843ed61663c6fcaf1917695f997f156a6e50b8e5a7b9ef81f589745083521c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ 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.28] - 2026-08-28
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Element-child reads pay only for elements** — the
|
|
13
|
+
interest-proportional principle applied to tree navigation:
|
|
14
|
+
- `#element_children` on element receivers rides the element-only
|
|
15
|
+
batch (`leptris_element_children`, opportunistic single
|
|
16
|
+
dispatch on the shared scratch): text/comment children are
|
|
17
|
+
never wrapped, and every kept element carries the ELEMENT hint
|
|
18
|
+
so the per-child `get_type` disappears. Cold pass over
|
|
19
|
+
big.xml's 25,000 items: 160 -> **117 ms per doc (-27%)**.
|
|
20
|
+
Non-element receivers (the document node) keep the filter.
|
|
21
|
+
- `#first_element_child` types candidate siblings with one raw
|
|
22
|
+
`get_type` each — nothing wrapped or cached until the element
|
|
23
|
+
is found (it previously full-wrapped leading text nodes).
|
|
24
|
+
- `#last_element_child` on element receivers wraps only the
|
|
25
|
+
batch's final pointer (it previously materialized every child,
|
|
26
|
+
text wraps included, to scan backwards). The first/last pair
|
|
27
|
+
across fresh docs: **-46%**.
|
|
28
|
+
- The all-kind `#children` path is untouched — the cold full-tree
|
|
29
|
+
walk battery stays at parity.
|
|
30
|
+
|
|
31
|
+
## [1.9.27] - 2026-08-28
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Arity-declared attrs interest in SAX** (round XIX's policy
|
|
36
|
+
completed): a handler whose `start_element` takes exactly one
|
|
37
|
+
argument declares name-only — the attribute walk (2N pointer
|
|
38
|
+
reads + N string pairs per start) never runs. Measured
|
|
39
|
+
elements-only on big.xml: 116 -> **40 ms per parse (2.9x; the
|
|
40
|
+
no-walk ceiling is 3.7x)**. This also fixes a crash: 1-argument
|
|
41
|
+
`start_element` handlers previously raised ArgumentError because
|
|
42
|
+
the callback always dispatched both arguments (Nokogiri raises
|
|
43
|
+
there too; we now honor the signature). Optional-argument and
|
|
44
|
+
splat handlers keep receiving pairs exactly as before.
|
|
45
|
+
|
|
46
|
+
### Meta
|
|
47
|
+
|
|
48
|
+
- Upstream check: no libleptris 1.9.8 exists (tarball probe 404s);
|
|
49
|
+
1.9.7 remains latest. Two perf hypotheses benchmarked and
|
|
50
|
+
retired this round: `Element#content` is already a memoized
|
|
51
|
+
one-C-call read, and `Node#traverse`'s C callback dispatch
|
|
52
|
+
(181 ms) beats Ruby children-recursion (551 ms) 3x — both stay.
|
|
53
|
+
|
|
8
54
|
## [1.9.26] - 2026-08-28
|
|
9
55
|
|
|
10
56
|
### Added
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -863,6 +863,32 @@ module Leptris
|
|
|
863
863
|
buffer.get_array_of_pointer(0, copied)
|
|
864
864
|
end
|
|
865
865
|
|
|
866
|
+
# Element-only child batch (the mirror of fetch_children over
|
|
867
|
+
# leptris_element_children): element receivers skip wrapping —
|
|
868
|
+
# and paying for — the text/comment children they will filter
|
|
869
|
+
# out anyway; every wrap carries the ELEMENT hint so the
|
|
870
|
+
# per-child get_type dispatch disappears too.
|
|
871
|
+
def self.fetch_element_children(el_ptr)
|
|
872
|
+
scratch = (Thread.current[:leptris_scratch] ||= {})
|
|
873
|
+
buffer = scratch[:pointers]
|
|
874
|
+
if buffer.nil?
|
|
875
|
+
buffer = scratch[:pointers] =
|
|
876
|
+
::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL)
|
|
877
|
+
end
|
|
878
|
+
cap = buffer.size / PTR_BYTES
|
|
879
|
+
copied = leptris_element_children(el_ptr, buffer, cap)
|
|
880
|
+
if copied == cap
|
|
881
|
+
total = leptris_element_children(el_ptr, nil, 0)
|
|
882
|
+
if total > cap
|
|
883
|
+
buffer.free
|
|
884
|
+
buffer = scratch[:pointers] =
|
|
885
|
+
::FFI::MemoryPointer.new(:pointer, total)
|
|
886
|
+
copied = leptris_element_children(el_ptr, buffer, total)
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
buffer.get_array_of_pointer(0, copied)
|
|
890
|
+
end
|
|
891
|
+
|
|
866
892
|
# XPath result-set batch (leptris_xpath_result_get_nodes_ex):
|
|
867
893
|
# the call fills out_kinds in the 4-value XPATH_NODE space
|
|
868
894
|
# (ELEMENT/ATTRIBUTE/TEXT/OTHER). ELEMENT is the only value
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -190,12 +190,17 @@ class Leptris::XML::Node
|
|
|
190
190
|
def first_element_child
|
|
191
191
|
return @first_element_child if memo_hit?(@first_element_child_version)
|
|
192
192
|
ensure_alive!
|
|
193
|
+
# Raw pointer scan: non-element siblings are typed with one C
|
|
194
|
+
# call each — never wrapped, never cached — and the found
|
|
195
|
+
# element carries the ELEMENT hint into the wrap.
|
|
193
196
|
ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
|
|
194
197
|
result = nil
|
|
195
198
|
until ptr.nil? || ptr.null?
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
result =
|
|
199
|
+
if Leptris::XML::FFI.leptris_node_get_type(ptr) ==
|
|
200
|
+
Leptris::XML::FFI::NODE_ELEMENT
|
|
201
|
+
result = Leptris::XML::Node.wrap(
|
|
202
|
+
ptr, @document, parent: as_element_or_self,
|
|
203
|
+
node_type: Leptris::XML::FFI::NODE_ELEMENT)
|
|
199
204
|
break
|
|
200
205
|
end
|
|
201
206
|
ptr = Leptris::XML::FFI.leptris_node_next_sibling(ptr)
|
|
@@ -208,12 +213,34 @@ class Leptris::XML::Node
|
|
|
208
213
|
end
|
|
209
214
|
|
|
210
215
|
def last_element_child
|
|
216
|
+
# Element receivers: the element-only batch fetches pointers
|
|
217
|
+
# without wrapping any text child; only the last is wrapped.
|
|
218
|
+
if is_a?(Leptris::XML::Element)
|
|
219
|
+
kids = Leptris::XML::FFI.fetch_element_children(@c_ptr)
|
|
220
|
+
return nil if kids.empty?
|
|
221
|
+
return Leptris::XML::Node.wrap(
|
|
222
|
+
kids.last, @document, parent: self,
|
|
223
|
+
node_type: Leptris::XML::FFI::NODE_ELEMENT)
|
|
224
|
+
end
|
|
211
225
|
children.reverse_each.find(&:element?)
|
|
212
226
|
end
|
|
213
227
|
|
|
214
228
|
def element_children
|
|
215
229
|
return @element_children if memo_hit?(@element_children_version)
|
|
216
|
-
|
|
230
|
+
ensure_alive!
|
|
231
|
+
# Element receivers ride the element-only batch: text/comment
|
|
232
|
+
# children are never wrapped (nor their get_type paid — the
|
|
233
|
+
# ELEMENT hint rides along). Other nodes keep the filter.
|
|
234
|
+
result =
|
|
235
|
+
if is_a?(Leptris::XML::Element)
|
|
236
|
+
parent = as_element_or_self
|
|
237
|
+
Leptris::XML::FFI.fetch_element_children(@c_ptr).map do |ptr|
|
|
238
|
+
Leptris::XML::Node.wrap(ptr, @document, parent: parent,
|
|
239
|
+
node_type: Leptris::XML::FFI::NODE_ELEMENT)
|
|
240
|
+
end
|
|
241
|
+
else
|
|
242
|
+
children.select(&:element?)
|
|
243
|
+
end
|
|
217
244
|
if @document
|
|
218
245
|
@element_children = result
|
|
219
246
|
@element_children_version = @document.version
|
|
@@ -105,9 +105,21 @@ class Leptris::XML::SAX::Parser
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
if overridden?(:start_element)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
# Arity-declared interest: a start_element that takes exactly
|
|
109
|
+
# one argument declares name-only — the attr walk (2N pointer
|
|
110
|
+
# reads + N string pairs per start; 3.7x of an elements-only
|
|
111
|
+
# parse on the 1.9 MB stream) never runs. It is also the only
|
|
112
|
+
# way a 1-arg handler can receive events: the two-argument
|
|
113
|
+
# call raises ArgumentError.
|
|
114
|
+
if @document.method(:start_element).arity == 1
|
|
115
|
+
s[:start_element] = callback(:void, [:pointer, :string, :pointer]) do |_, name, _|
|
|
116
|
+
handler.start_element(utf8(name))
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
s[:start_element] = callback(:void, [:pointer, :string, :pointer]) do |_, name, attrs_ptr|
|
|
120
|
+
attrs = walk_attr_array(attrs_ptr)
|
|
121
|
+
handler.start_element(utf8(name), attrs)
|
|
122
|
+
end
|
|
111
123
|
end
|
|
112
124
|
end
|
|
113
125
|
|