leptris 1.9.10 → 1.9.11

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: 4e7dca7e58492747ba0b5843937a7a8a914afa52f4f7271e2b808d1b895a2ded
4
- data.tar.gz: bf399063035b7cacd20893813e89f85582594ef7644ab8b2ef076772cc40c094
3
+ metadata.gz: 4e218d64aa7c30174d4fe7a22e47cc78c35f07cbfe3d0f22430f3ba411651143
4
+ data.tar.gz: 030eed3a6b5a7387da757ae20406591490a1f55893425d63a443c77631903a6f
5
5
  SHA512:
6
- metadata.gz: df766bafa6dc7d590909f4f6d2a475446115b5d9cd1d57605558654959e699188f1a36329248c653b501e5e432a9a604533e01dd476e9b350a5510be27fa7ca3
7
- data.tar.gz: c0c8f0f164ceadd0c24d476ca8c4948582e091379f7f976a0fdbfc073d482a97c8a80bf29ad6601f2ad474e0fd4464c5e903cb382b56350be2a6c07a1ef0ecf6
6
+ metadata.gz: 45c707da19fc4114c5f35641b799e104130a8d7c93d8fd365fe72cafec0b894f80e06dba7a73d0109b0fde77868d5db3836035411a3d9951d2f5c3db3c7c8a0f
7
+ data.tar.gz: '06388d993c6331e3089ad5c9f87056d978096f8bbd56598a1e8abdd37db03575e7d79989f3ed9ef5ba836d58804daaa1039d1b22e2c30c6b1bc4fb59d88fbd85'
data/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ 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.11] - 2026-08-26
9
+
10
+ ### Changed
11
+
12
+ - **Readonly `Element#[]` serves from the memoized attributes
13
+ hash** (materializing on demand): repeated reads become hash
14
+ lookups — no FFI dispatch, no lifetime guard. Completes the
15
+ readonly contract on its hottest member. Writable documents keep
16
+ the direct path (values can change).
17
+ - **Materialized NodeSets stop re-batching**: `each`, `[]`, and
18
+ `length` consult the materialized array first; iterating or
19
+ indexing after `to_a` no longer re-pays the count + fetch + wrap
20
+ pass. Repeated iteration measures 2.4x faster.
21
+ - **NodeSet negative indexes are consistent**: `ns[-1]` answers the
22
+ last element for lazy and eager sets alike (Ruby-Array slice
23
+ semantics, Nokogiri parity); previously only eager sets
24
+ supported them.
25
+ - **SAX start_element attributes build pairs in one pass** — no
26
+ intermediate flat string array or each_slice enumerator per
27
+ event (~13% on the SAX parse loop).
28
+
29
+ ### Measured and rejected
30
+
31
+ - A compiled-expression cache for ad-hoc xpath (the round's main
32
+ hypothesis): compiled vs ad-hoc eval measured within 2.5% on
33
+ 500-member loops — the engine's expression parse is effectively
34
+ free. Killed before implementation.
35
+
8
36
  ## [1.9.10] - 2026-08-26
9
37
 
10
38
  ### Changed
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.10"
4
+ VERSION = "1.9.11"
5
5
  end
@@ -32,6 +32,14 @@ class Leptris::XML::Element < Leptris::XML::Node
32
32
  end
33
33
 
34
34
  def [](key)
35
+ # Readonly: the attributes hash cannot go stale (ADR 0003) and
36
+ # materializes on demand — repeated reads become hash lookups,
37
+ # with no dispatch and no lifetime guard (a hash read cannot
38
+ # use-after-free).
39
+ if @document&.readonly?
40
+ cached = attributes[key.to_s]
41
+ return cached.nil? ? nil : cached.value
42
+ end
35
43
  ensure_alive!
36
44
  Leptris::XML::FFI.leptris_element_attribute(@c_ptr, key.to_s)
37
45
  end
@@ -41,7 +41,8 @@ class Leptris::XML::NodeSet
41
41
  end
42
42
 
43
43
  def length
44
- @result_ptr ? Leptris::XML::FFI.leptris_xpath_result_count(@result_ptr) : @array.length
44
+ return @array.length if @array
45
+ @result_ptr ? Leptris::XML::FFI.leptris_xpath_result_count(@result_ptr) : 0
45
46
  end
46
47
  alias_method :size, :length
47
48
 
@@ -49,19 +50,26 @@ class Leptris::XML::NodeSet
49
50
  length == 0
50
51
  end
51
52
 
53
+ # The materialized array (after to_a) is authoritative: every
54
+ # reader consults it first so a materialized set stops paying the
55
+ # batch fetch on each iteration or index.
52
56
  def [](idx)
57
+ return @array[idx] if @array
58
+ # Negative indexes are Ruby-Array semantics (Nokogiri's NodeSet
59
+ # is slice-like); materialize rather than answer nil
60
+ # inconsistently with an eager set.
61
+ return to_a[idx] if idx.negative?
53
62
  if @result_ptr
54
63
  return nil if idx < 0 || idx >= length
55
64
  ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(@result_ptr, idx)
56
65
  return nil if ptr.null?
57
66
  Leptris::XML::Node.wrap(ptr, @document)
58
- else
59
- @array[idx]
60
67
  end
61
68
  end
62
69
 
63
70
  def each
64
71
  return enum_for(:each) unless block_given?
72
+ return @array.each { |n| yield n } if @array
65
73
  if @result_ptr
66
74
  # Batch-fetch all node pointers through the FFI seam
67
75
  # (leptris_xpath_result_get_nodes_ex copies every node kind,
@@ -148,8 +148,19 @@ class Leptris::XML::SAX::Parser
148
148
  end
149
149
 
150
150
  # The C `const char** attrs` is a NULL-terminated flat array of
151
- # name/value pairs; CStringArray owns the format, we slice into pairs.
151
+ # name/value pairs. Build the [name, value] pairs in one pass —
152
+ # no intermediate flat string array, no each_slice enumerator.
152
153
  def walk_attr_array(attrs_ptr)
153
- Leptris::XML::CStringArray.to_ruby(attrs_ptr).each_slice(2).to_a
154
+ ptr_size = ::FFI.type_size(:pointer)
155
+ pairs = []
156
+ i = 0
157
+ loop do
158
+ name_ptr = attrs_ptr.get_pointer(i * ptr_size)
159
+ break if name_ptr.null?
160
+ value_ptr = attrs_ptr.get_pointer((i + 1) * ptr_size)
161
+ pairs << [utf8(name_ptr.read_string), utf8(value_ptr.read_string)]
162
+ i += 2
163
+ end
164
+ pairs
154
165
  end
155
166
  end
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.10
4
+ version: 1.9.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.