leptris 1.9.31-x86_64-linux → 1.9.32-x86_64-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: bd0e0707b4347bc6678ac23d73051751a9c5f6103491f1b3289ad125555416b6
4
- data.tar.gz: b3bb1712dd508747336326e9fc7d32ed031c5e13f3b77993b288ae6379c48d75
3
+ metadata.gz: e0126843719aa5ffea0bfe6d60e13a57f6a4e8dc1ac0cd92f0c91b203ee5853a
4
+ data.tar.gz: 50678d08aa8cba33138d447731cd267e0a3ca7be7df4f7a5a9121a5e7bc98de8
5
5
  SHA512:
6
- metadata.gz: b9fcbf20769ee53bc6b6586491c47bcc8f72ba870aeb260c62c0bba7b2b55b6c0c937cc0e9873345d7997e8842455d50078ff7618231010d2fc958c1233734ef
7
- data.tar.gz: 9286711bda1ce1609ff52b7c9edf7c97871380f341b84b15f0ecad0101a63244779aa32b042b2c02e541208f4a0e14ea89d4e0ca49175473a82d1058100757e2
6
+ metadata.gz: 33976c129c10325e338a9fcfb0df286cb99bbcb5a5fe5541fdf0b310d2a7c42babc5704cc60f7dce61eb5bbe399fd65d059cbee97f0e91303bb580ba1ff8fbc3
7
+ data.tar.gz: 5939d862df5f8e412815219962d7261dbf533d8ed0ee3d038cd4a0961d4c97d8ac8addf67c58db80f5b64fbd5e753eac0ee36323aabfb3938a0558fd550b643c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ 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.32] - 2026-08-28
9
+
10
+ ### Fixed
11
+
12
+ - **traverse is subtree-bounded (leptris-ruby#89)**: the C walker
13
+ was never bounded — after visiting the receiver it pushed the
14
+ receiver's next sibling and continued to the end of the document
15
+ chain, so `element.traverse` swept following siblings and their
16
+ subtrees, and `root.traverse` swept the epilog. In post-order the
17
+ receiver is the LAST node of its own subtree, so the callback now
18
+ returns non-zero at self — the C loop honors it and the walk
19
+ stops exactly at the boundary.
20
+ - **traverse re-raises callback exceptions (leptris-ruby#90)**: a
21
+ rescue inside the FFI callback stashes the exception and returns
22
+ non-zero (aborting the walk); the stashed exception is re-raised
23
+ after `leptris_node_traverse` returns. Previously the dispatch
24
+ silently swallowed Ruby exceptions and the walk continued with
25
+ partially processed data.
26
+ - **built documents list the attached root (leptris-ruby#91)**:
27
+ libleptris's `document_set_root` does not register the root into
28
+ the document node's child chain, so `Document#children` missed it
29
+ until another document mutation refreshed the chain. `#children`
30
+ now splices the attached root in: a replaced root's stale chain
31
+ entry is dropped and the new root inherits its position (the
32
+ prolog/epilog split follows the old slot); on rootless chains
33
+ placement falls back to document-order comparison. Parsed and
34
+ built documents read the same.
35
+ - **document-level PI writes name the contract (leptris-ruby#92)**:
36
+ parse-created document-level PIs carry no document linkage in
37
+ libleptris, so `target=`/`data=` fail INVALID_ARG and `unlink`
38
+ NOT_FOUND while tree-level and `Document#add_pi` PIs mutate fine.
39
+ Those failures now raise a descriptive error naming the contract
40
+ (mutable alternatives included) instead of a bare "Invalid
41
+ argument". Full write-through and a remove API need C surface
42
+ (filed upstream); the binding cannot supply them.
43
+
8
44
  ## [1.9.31] - 2026-08-28
9
45
 
10
46
  ### Fixed
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.31"
4
+ VERSION = "1.9.32"
5
5
  end
@@ -166,9 +166,44 @@ class Leptris::XML::Document
166
166
  # The document's children, via the document node: prolog
167
167
  # comments/PIs, the root element, epilog comments/PIs, in
168
168
  # document order (Nokogiri-parity shape).
169
+ #
170
+ # On programmatically built documents the C document node's chain
171
+ # misses a root attached via #root= until some other document
172
+ # mutation refreshes it (leptris-ruby#91 — libleptris's
173
+ # document_set_root does not register into the chain). The merge
174
+ # below splices the attached root in by document order whenever
175
+ # the chain lacks it, so parsed and built documents read the
176
+ # same. Prolog/epilog placement uses node_compare against the
177
+ # root.
169
178
  def children
170
179
  doc_node = node
171
- doc_node ? doc_node.children : Leptris::XML::NodeSet.new(self, [])
180
+ return Leptris::XML::NodeSet.new(self, []) if doc_node.nil?
181
+ kids = doc_node.children.to_a
182
+ root_ptr = Leptris::XML::FFI.leptris_document_root(@c_ptr)
183
+ return kids if root_ptr.null?
184
+ return kids if kids.any? { |child| child.c_ptr == root_ptr }
185
+ root = Leptris::XML::Node.wrap(root_ptr, self)
186
+ # A replaced root stays in the C chain until another document
187
+ # mutation refreshes it — the chain's element slots other than
188
+ # the current root are stale old roots. The new root inherits
189
+ # the first stale slot's position (the prolog/epilog split
190
+ # follows where the old root sat); with no stale element
191
+ # (programmatically built documents) placement falls back to
192
+ # document-order comparison.
193
+ stale = kids.select do |child|
194
+ child.element? && child.c_ptr != root_ptr
195
+ end
196
+ if stale.any?
197
+ kept = kids.reject { |child| stale.include?(child) }
198
+ slot = kids.index(stale.first)
199
+ kept.insert(slot, root)
200
+ Leptris::XML::NodeSet.new(self, kept)
201
+ else
202
+ prolog, epilog = kids.partition do |child|
203
+ Leptris::XML::FFI.leptris_node_compare(child.c_ptr, root_ptr).negative?
204
+ end
205
+ Leptris::XML::NodeSet.new(self, prolog + [root] + epilog)
206
+ end
172
207
  end
173
208
 
174
209
  # Attach +element+ as the document's root element. The element must
@@ -263,27 +263,56 @@ class Leptris::XML::Node
263
263
 
264
264
  def unlink
265
265
  ensure_writable!
266
- Leptris::XML::FFI.check_status(
267
- Leptris::XML::FFI.leptris_node_unlink(@c_ptr))
266
+ rc = Leptris::XML::FFI.leptris_node_unlink(@c_ptr)
267
+ if rc == Leptris::XML::FFI::LEPTRIS_ERROR_NOT_FOUND && processing_instruction?
268
+ raise Leptris::XML::Error,
269
+ Leptris::XML::ProcessingInstruction::READ_ONLY_NATIVE
270
+ end
271
+ Leptris::XML::FFI.check_status(rc)
268
272
  @parent = nil
269
273
  self
270
274
  end
271
275
  alias_method :remove, :unlink
272
276
 
273
- # Walks the subtree in post-order DFS (matches Nokogiri's semantics).
277
+ # Walks the subtree in post-order DFS (matches Nokogiri's
278
+ # semantics): the receiver, its descendants, nothing else.
274
279
  #
275
280
  # One FFI call dispatches the whole walk; the C engine invokes the
276
281
  # callback once per visited node (the only per-node cost is the
277
282
  # C-to-Ruby callback dispatch, not FFI round-trips).
283
+ #
284
+ # Subtree-bounded by abort-at-self (leptris-ruby#89): the C
285
+ # walker was never bounded — after visiting the receiver it
286
+ # pushes the receiver's NEXT SIBLING and continues to the end of
287
+ # the document chain. In post-order the receiver is the LAST
288
+ # node of its own subtree, so returning non-zero at self stops
289
+ # the walk exactly at the boundary (the C loop honors a non-zero
290
+ # callback return). The self comparison is by address; the
291
+ # receiver's handle is stable for the walk's duration.
292
+ #
293
+ # Exceptions raised by the block are re-raised after the walk
294
+ # (leptris-ruby#90): a rescue inside the callback stashes the
295
+ # exception and returns non-zero, aborting the C walk — without
296
+ # it the FFI dispatch silently swallowed the exception and the
297
+ # walk continued with partially processed data.
278
298
  def traverse
279
299
  return enum_for(:traverse) unless block_given?
280
300
  ensure_alive!
301
+ error = nil
302
+ self_address = @c_ptr.address
281
303
  callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
282
- yield Leptris::XML::Node.wrap(node_ptr, @document)
283
- 0
304
+ begin
305
+ yield Leptris::XML::Node.wrap(node_ptr, @document)
306
+ node_ptr.address == self_address ? 1 : 0
307
+ rescue Exception => e # rubocop:disable Lint/RescueException
308
+ error = e
309
+ 1
310
+ end
284
311
  end
285
312
  Leptris::XML::FFI.leptris_node_traverse(
286
313
  @c_ptr, Leptris::XML::FFI::TRAVERSE_POST_ORDER, callback, nil)
314
+ raise error if error
315
+ self
287
316
  end
288
317
 
289
318
  def path
@@ -25,17 +25,34 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
25
25
  result
26
26
  end
27
27
 
28
+ # Parse-created document-level PIs carry no document linkage in
29
+ # libleptris (leptris-ruby#92): the C setters reject them with
30
+ # INVALID_ARG while tree-level and Document#add_pi PIs mutate
31
+ # fine. Map that failure to the contract instead of a bare
32
+ # "Invalid argument".
33
+ READ_ONLY_NATIVE =
34
+ "this PI is a parse-created document-level PI — its node has " \
35
+ "no document linkage in libleptris, so target/data cannot be " \
36
+ "written and it cannot be unlinked (leptris-ruby#92); " \
37
+ "tree-level and Document#add_pi PIs are mutable"
38
+
28
39
  def target=(new_target)
29
40
  ensure_writable!
30
- Leptris::XML::FFI.check_status(
31
- Leptris::XML::FFI.leptris_pi_node_set_target(@c_ptr, new_target.to_s))
41
+ rc = Leptris::XML::FFI.leptris_pi_node_set_target(
42
+ @c_ptr, new_target.to_s)
43
+ raise Leptris::XML::Error, READ_ONLY_NATIVE if
44
+ rc == Leptris::XML::FFI::LEPTRIS_ERROR_INVALID_ARG
45
+ Leptris::XML::FFI.check_status(rc)
32
46
  new_target
33
47
  end
34
48
 
35
49
  def data=(new_data)
36
50
  ensure_writable!
37
- Leptris::XML::FFI.check_status(
38
- Leptris::XML::FFI.leptris_pi_node_set_data(@c_ptr, new_data.to_s))
51
+ rc = Leptris::XML::FFI.leptris_pi_node_set_data(
52
+ @c_ptr, new_data.to_s)
53
+ raise Leptris::XML::Error, READ_ONLY_NATIVE if
54
+ rc == Leptris::XML::FFI::LEPTRIS_ERROR_INVALID_ARG
55
+ Leptris::XML::FFI.check_status(rc)
39
56
  new_data
40
57
  end
41
58
  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.31
4
+ version: 1.9.32
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Ribose Inc.