leptris 1.9.14-aarch64-linux → 1.9.16-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 +31 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +11 -7
- data/lib/leptris/xml/node.rb +11 -2
- data/lib/leptris/xml/searchable.rb +42 -5
- 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: f6960184588bed5ebd9fa8fbf64fe97bb8476b433ba43b5176568f20b9dd561b
|
|
4
|
+
data.tar.gz: 739e787fbd3c921bcb2ac4cdf776b3f85bc7ec2fe870b8f94f9366cd3aacc710
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2588d661f45aec8859681b381fffd2fb05b4600f2888c826ad86a1c43f8ad86d7a8fc95b8ce4d5ae803772275066e4f43e67f09bf0592d5648d732383081168e
|
|
7
|
+
data.tar.gz: d01eb46bdd19bbdfdf26c6fb278111291f8713439d088f205f7985ba46f31696c344c864f8d95229d4e389c7bc857bdfaae25721cf49d8475c71936c2344b8e1
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ 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.16] - 2026-08-27
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **at_xpath / at_css take the single-node path**: a new
|
|
13
|
+
`wrap_xpath_first_result` seam answers nodeset results via
|
|
14
|
+
get_node(0) + wrap + free — no NodeSet container, no AutoPointer,
|
|
15
|
+
one fewer FFI than xpath().first; scalar results keep full-wrapper
|
|
16
|
+
semantics. Measured on the canonical scraper loop: at_xpath 0.018 s
|
|
17
|
+
-> 0.005 s (3.6x — the per-iteration container churn was GC
|
|
18
|
+
pressure the old path paid twice), at_css ~24%. Equivalence with
|
|
19
|
+
xpath().first is spec-pinned across nodeset/empty/scalar shapes.
|
|
20
|
+
Also drops a vestigial `.send(:from_result)` on a public factory.
|
|
21
|
+
|
|
22
|
+
## [1.9.15] - 2026-08-27
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`Element#[]` serves bare names from the versioned attributes
|
|
27
|
+
hash on every document kind** (writable included): materializes
|
|
28
|
+
on demand, invalidates through the mutation gate. Qualified names
|
|
29
|
+
(with a colon) still route to the engine — they resolve through
|
|
30
|
+
in-scope declarations where the written prefix never matters,
|
|
31
|
+
which a written-name-keyed hash cannot answer. Writable
|
|
32
|
+
attribute loops reach readonly parity: 0.196 s -> 0.101 s (2x);
|
|
33
|
+
the readonly special-case branch is deleted. The qualified/bare
|
|
34
|
+
split is spec-pinned (cross-prefix match, undeclared-prefix nil).
|
|
35
|
+
- **`first_element_child` memoizes** with version invalidation
|
|
36
|
+
(structural mutations bump): 0.058 s -> 0.015 s (4x) on the
|
|
37
|
+
repeat loop.
|
|
38
|
+
|
|
8
39
|
## [1.9.14] - 2026-08-27
|
|
9
40
|
|
|
10
41
|
### Changed
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -35,16 +35,20 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def [](key)
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
# BARE names serve from the versioned attributes hash (written
|
|
39
|
+
# name == the only legal key for a no-namespace attribute);
|
|
40
|
+
# materializes on demand, invalidates through the mutation gate
|
|
41
|
+
# (ADR 0003's 1.9.14 extension); a hash read cannot
|
|
42
|
+
# use-after-free. QUALIFIED names (with a colon) go to the
|
|
43
|
+
# engine: they resolve through in-scope declarations, where the
|
|
44
|
+
# written prefix never matters — the hash cannot answer them.
|
|
45
|
+
name = key.to_s
|
|
46
|
+
if @document && !name.include?(":")
|
|
47
|
+
cached = attributes[name]
|
|
44
48
|
return cached.nil? ? nil : cached.value
|
|
45
49
|
end
|
|
46
50
|
ensure_alive!
|
|
47
|
-
Leptris::XML::FFI.leptris_element_attribute(@c_ptr,
|
|
51
|
+
Leptris::XML::FFI.leptris_element_attribute(@c_ptr, name)
|
|
48
52
|
end
|
|
49
53
|
alias_method :attr, :[]
|
|
50
54
|
alias_method :get_attribute, :[]
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -179,14 +179,23 @@ class Leptris::XML::Node
|
|
|
179
179
|
alias_method :previous, :previous_sibling
|
|
180
180
|
|
|
181
181
|
def first_element_child
|
|
182
|
+
return @first_element_child if memo_hit?(@first_element_child_version)
|
|
182
183
|
ensure_alive!
|
|
183
184
|
ptr = Leptris::XML::FFI.leptris_node_first_child(@c_ptr)
|
|
185
|
+
result = nil
|
|
184
186
|
until ptr.nil? || ptr.null?
|
|
185
187
|
node = Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
|
|
186
|
-
|
|
188
|
+
if node.element?
|
|
189
|
+
result = node
|
|
190
|
+
break
|
|
191
|
+
end
|
|
187
192
|
ptr = Leptris::XML::FFI.leptris_node_next_sibling(ptr)
|
|
188
193
|
end
|
|
189
|
-
|
|
194
|
+
if @document
|
|
195
|
+
@first_element_child = result
|
|
196
|
+
@first_element_child_version = @document.version
|
|
197
|
+
end
|
|
198
|
+
result
|
|
190
199
|
end
|
|
191
200
|
|
|
192
201
|
def last_element_child
|
|
@@ -24,8 +24,24 @@ module Leptris::XML::Searchable
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def at_xpath(*paths)
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
handler, ns, _vars = parse_search_args(paths)
|
|
28
|
+
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
29
|
+
expr = paths.join(" | ")
|
|
30
|
+
|
|
31
|
+
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
32
|
+
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
33
|
+
|
|
34
|
+
result_ptr =
|
|
35
|
+
if ns && !ns.empty?
|
|
36
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
37
|
+
else
|
|
38
|
+
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
39
|
+
end
|
|
40
|
+
if result_ptr.null?
|
|
41
|
+
raise Leptris::XML::XPathError,
|
|
42
|
+
Leptris::XML::FFI.leptris_last_error.to_s
|
|
43
|
+
end
|
|
44
|
+
Leptris::XML::Searchable.wrap_xpath_first_result(document, result_ptr)
|
|
29
45
|
end
|
|
30
46
|
|
|
31
47
|
def search(*args)
|
|
@@ -53,8 +69,13 @@ module Leptris::XML::Searchable
|
|
|
53
69
|
end
|
|
54
70
|
|
|
55
71
|
def at_css(*args)
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
handler, ns, _ = parse_search_args(args)
|
|
73
|
+
raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
|
|
74
|
+
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
75
|
+
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
76
|
+
expr = args.map { |r| Leptris::XML::CssToXPath.convert(r, prefix: prefix) }
|
|
77
|
+
.join(" | ")
|
|
78
|
+
at_xpath(expr)
|
|
58
79
|
end
|
|
59
80
|
|
|
60
81
|
protected
|
|
@@ -86,6 +107,22 @@ module Leptris::XML::Searchable
|
|
|
86
107
|
%r{\A(\./|/|\.\.|\.)}.match?(str)
|
|
87
108
|
end
|
|
88
109
|
|
|
110
|
+
# Single-node seam beside wrap_xpath_result: a nodeset answers
|
|
111
|
+
# via result_get_node(0) + wrap + free — no NodeSet container, no
|
|
112
|
+
# AutoPointer, one fewer FFI than xpath().first; scalars keep the
|
|
113
|
+
# full-wrapper semantics.
|
|
114
|
+
def self.wrap_xpath_first_result(document, result_ptr)
|
|
115
|
+
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
116
|
+
if type == Leptris::XML::FFI::XPATH_NODESET
|
|
117
|
+
ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(result_ptr, 0)
|
|
118
|
+
node = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, document)
|
|
119
|
+
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|
|
120
|
+
node
|
|
121
|
+
else
|
|
122
|
+
wrap_xpath_result(document, result_ptr)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
89
126
|
# Shared by Searchable#xpath and Leptris::XML::XPath (compiled
|
|
90
127
|
# expressions): wraps a raw XPathResult pointer into the
|
|
91
128
|
# Ruby-typed result and frees the C handle.
|
|
@@ -93,7 +130,7 @@ module Leptris::XML::Searchable
|
|
|
93
130
|
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
94
131
|
case type
|
|
95
132
|
when Leptris::XML::FFI::XPATH_NODESET
|
|
96
|
-
Leptris::XML::NodeSet.
|
|
133
|
+
Leptris::XML::NodeSet.from_result(document, result_ptr)
|
|
97
134
|
when Leptris::XML::FFI::XPATH_BOOLEAN
|
|
98
135
|
v = Leptris::XML::FFI.leptris_xpath_result_boolean(result_ptr) != 0
|
|
99
136
|
Leptris::XML::FFI.leptris_xpath_result_free(result_ptr)
|