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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c96f59841d0606a790ec372576cdb05f50bbc39c43cdf1188e7a4535068a7a8f
4
- data.tar.gz: e3396a5ec39b5638f20a2254da0796f6330ca689f62e2fcad939850a42662f64
3
+ metadata.gz: f6960184588bed5ebd9fa8fbf64fe97bb8476b433ba43b5176568f20b9dd561b
4
+ data.tar.gz: 739e787fbd3c921bcb2ac4cdf776b3f85bc7ec2fe870b8f94f9366cd3aacc710
5
5
  SHA512:
6
- metadata.gz: a6cc9bb4c0ba7b0c39ced816d8435370947d6e051df16d1d03a899c5fbbdd1a7ba1e0c7e0a05f7879874a4b20c5db7b2f7b639138eb0137bbb1276e1288b7b59
7
- data.tar.gz: 12846d96eec9f9a3d681bdb1bd9a2e29807a21575adc209851bdb4b3067f8c5999732faa9a4bdfad0af1ac6e55cdac6f52c832d886623f137c8b0aa867bc5ba2
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.14"
4
+ VERSION = "1.9.16"
5
5
  end
@@ -35,16 +35,20 @@ class Leptris::XML::Element < Leptris::XML::Node
35
35
  end
36
36
 
37
37
  def [](key)
38
- # Readonly: the attributes hash cannot go stale (ADR 0003) and
39
- # materializes on demand — repeated reads become hash lookups,
40
- # with no dispatch and no lifetime guard (a hash read cannot
41
- # use-after-free).
42
- if readonly_document?
43
- cached = attributes[key.to_s]
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, key.to_s)
51
+ Leptris::XML::FFI.leptris_element_attribute(@c_ptr, name)
48
52
  end
49
53
  alias_method :attr, :[]
50
54
  alias_method :get_attribute, :[]
@@ -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
- return node if node.element?
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
- nil
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
- result = xpath(*paths)
28
- result.is_a?(Leptris::XML::NodeSet) ? result.first : result
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
- result = css(*args)
57
- result.is_a?(Leptris::XML::NodeSet) ? result.first : result
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.send(:from_result, document, result_ptr)
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)
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.14
4
+ version: 1.9.16
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.