leptris 1.9.27 → 1.9.28
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 +23 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +26 -0
- data/lib/leptris/xml/node.rb +31 -4
- 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: 5269fdd7c4c775fa14fa07fcaecd4356d6d7b0a04ea0de83f10b8efee3cae4df
|
|
4
|
+
data.tar.gz: 2de7f895e3e4fe444375fc800e867f751fd07d5598e09bde80d41d0df2c166bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e8e507336b7348bba069dba172842a76463939bd472483d5bbba3785d9aa2b8a0a928f484254ea642cfc1cedfc9b1fa29b02adaaecf5675c9881527c54a51ee
|
|
7
|
+
data.tar.gz: 81ac2512ce3d76fe7332e7ba53ca5f60d45358271b79ee47652f4043756d7914a0f24efb4031c30401dcf41a3d33adaea6fbfa52aa8993f19b746c11a4a63b52
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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
|
+
|
|
8
31
|
## [1.9.27] - 2026-08-28
|
|
9
32
|
|
|
10
33
|
### Fixed
|
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
|