leptris 1.9.42 → 1.9.44

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: 5de687a1b7597e0bdf6ff80f85566435d9927e136a86ec758a554928d85910d2
4
- data.tar.gz: 9171a19d4620e4d482c573e1f21e76a8ee253b175a8df34569e9711f1fcc42e4
3
+ metadata.gz: 9895934a1c086857555795905c15ab4f74ef053804e489674192bc7f4cc70bf3
4
+ data.tar.gz: b1bf31ac8c0ef5f718d78d4ae0654218cd3c212adf33e75628326949644032d1
5
5
  SHA512:
6
- metadata.gz: b2dadeb0a139c9a6396dd82defd080dc014e056a8959321a7f7dff13851503d8d53b6a7b1dc4f987a4bb131ce9054e3c0073d7234d6a8ff0e73caae2fc71cf7e
7
- data.tar.gz: 1648229492701f2805e405475b7abf4f59108844d79da56118f47e15fdb048ebf03f5b7a1d4c3f48c566e2c13b1881b50b7a09df9512fcb2cd928ae045260f3a
6
+ metadata.gz: 536c0b5a7ddb425395b077f5a6a0b40ebc893c6037ba71277a1b6eaa70b9769e977b03b9ed0f81ccf6d9a70e11c4f4095b8ab3e473268f2cb38306ea897d28e4
7
+ data.tar.gz: d34c001bf6840c12d3e44a0253166938523f57c0e935a6dfa4560cea575b02af043d25caf0a491a77810726b168c64e556c63b825355eb3ff23ffbbf010848c5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,61 @@ 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.44] - 2026-08-31
9
+
10
+ ### Added
11
+
12
+ - **libleptris 1.9.19–1.9.22 lockstep** (audit 255/255, three new
13
+ symbols): the #653 hold is lifted — the reported parse
14
+ "regression" was an ill-formed fixture (stray `</y>`) plus a
15
+ stale-build bisect artifact; forced-clean rebuilds show 1.9.18
16
+ and 1.9.21+ identical on every well-formed shape (verified and
17
+ withdrawn on the issue).
18
+ - **`Node#visit`** (upstream #645a, `leptris_node_visit`): one C
19
+ call over the subtree — elements yield `(node, true/false,
20
+ depth)` enter/leave pairs, other kinds once, depth from the
21
+ receiver; the document node walks the document chain. No
22
+ NodeSet, pointer array, or children memo per level — the leanest
23
+ full-subtree iteration the binding offers (#645a's walk lever;
24
+ consumers can now beat the children-recursion allocation floor).
25
+ - **Indent unit with Nokogiri semantics (leptris-ruby#109,
26
+ upstream #633)**: `to_xml(indent: 2, indent_text: "\t")` — the
27
+ unit string replaces the default spaces, repeated `indent`
28
+ times per depth level, standard layout otherwise
29
+ (byte-identical to Nokogiri's output modulo their trailing
30
+ newline, which moxml normalizes). `indent_text: true` keeps the
31
+ display form; `false` the default. Wired through the sized
32
+ ext-serialize entry (PR #107's substance — the audit requires it
33
+ attached with the lockstep, so it rides here; the engine emits
34
+ one unit copy per level and the binding multiplies unit x indent
35
+ to reach Nokogiri's repeat count).
36
+
37
+ ## [1.9.43] - 2026-08-31
38
+
39
+ ### Fixed
40
+
41
+ - **Pull prefix events surface (leptris-ruby#648's residual)**: the
42
+ engine's pull stream has always carried start/end prefix events
43
+ (codes 8/9; the default namespace's prefix is legitimately `""`)
44
+ — the binding's type map lacked the kinds, silently delivering
45
+ nil-typed Events that the 1.9.42 staging guard then misread as
46
+ corruption on healthy engines. `:start_prefix`/`:end_prefix` now
47
+ map correctly on both cursor and batch paths, and the guard's
48
+ name check only invalidates kinds that must carry names
49
+ (start/end element, PI target). Engine 1.9.21's staging fix
50
+ (upstream #648) verified clean through the batch path once the
51
+ kinds were mapped.
52
+
53
+ ### Meta
54
+
55
+ - The 1.9.19–1.9.21 lockstep is HELD: engine 1.9.19+ carries a DOM
56
+ parse regression — a self-closing element followed by text inside
57
+ a depth-2 parent fails to parse (`<div><p><br/>hello</p></div>`)
58
+ — filed as leptris/leptris#653. `Node#visit` (the #645a
59
+ wrap-free walk), the sized ext-serialize entry (PR #107's
60
+ substance), and this pull fix's engine-1.9.21 verification are
61
+ parked on a branch awaiting the fix.
62
+
8
63
  ## [1.9.42] - 2026-08-30
9
64
 
10
65
  ### Fixed
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.18"
11
+ LIBLEPTRIS_VERSION = "1.9.22"
12
12
 
13
13
  CMAKE_FLAGS = %w[
14
14
  -DCMAKE_BUILD_TYPE=Release
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.42"
4
+ VERSION = "1.9.44"
5
5
  end
@@ -266,13 +266,21 @@ class Leptris::XML::Document
266
266
  end
267
267
  alias_method :internal_subset, :doctype
268
268
 
269
- # indent_text: true selects the display form (libleptris 1.9.9,
270
- # #129): text and mixed content indent too — the output is
271
- # display-oriented and not round-trip-guaranteed.
269
+ # indent_text selects the ext-serializer knob (libleptris 1.9.22):
270
+ # a STRING is the indent unit with Nokogiri's semantics — the unit
271
+ # replaces the default spaces, one copy per depth level, standard
272
+ # layout (requires indent > 0); true selects the display form
273
+ # (1.9.9 #129 — text and mixed content indent too; output is
274
+ # display-oriented and not round-trip-guaranteed).
272
275
  def to_xml(indent: 0, no_decl: false, encoding: nil, indent_text: false)
273
276
  raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
274
277
  return "" if @c_ptr.nil?
275
- if indent_text
278
+ case indent_text
279
+ when String
280
+ return Leptris::XML::Serialization.to_xml_indent_unit(
281
+ @c_ptr, indent_text, indent: indent, no_decl: no_decl,
282
+ encoding: encoding)
283
+ when true
276
284
  return Leptris::XML::Serialization.to_xml_display(
277
285
  @c_ptr, indent: indent, no_decl: no_decl, encoding: encoding)
278
286
  end
@@ -72,10 +72,13 @@ module Leptris
72
72
  :encoding, :pointer
73
73
  end
74
74
 
75
- # LeptrisSerializeExtOptions (1.9.9, #129): display-form
76
- # serialization knobs beyond the ABI-frozen options struct.
75
+ # LeptrisSerializeExtOptions (1.9.9 #129 display form; 1.9.22
76
+ # #633/#109 indent unit): knobs beyond the ABI-frozen options
77
+ # struct. MAY grow — always pass its size through the sized
78
+ # entry.
77
79
  class SerializeExtStruct < ::FFI::Struct
78
- layout :indent_text, :int
80
+ layout :indent_text, :int,
81
+ :indent_unit, :pointer
79
82
  end
80
83
 
81
84
  # Mirrors LeptrisParseOptions (libleptris >= 1.9.0 carries the
@@ -154,6 +157,12 @@ module Leptris
154
157
  # 1.9.7, upstream #580; the libxml2 model).
155
158
  attach_function :leptris_document_node,
156
159
  [:leptris_document], :leptris_node_ref
160
+ # Wrap-free subtree visitation (1.9.20, upstream #645a):
161
+ # enter/leave pairs for elements, depth from the walk's root,
162
+ # one C call — no NodeSet/Array churn per level. The document
163
+ # node walks the document child chain.
164
+ attach_function :leptris_node_visit,
165
+ [:leptris_node_ref, :pointer, :pointer], :void
157
166
  # Document-level PIs (v1.6.0): not tree nodes — enumerate via
158
167
  # these accessors only.
159
168
  attach_function :leptris_document_pi_count,
@@ -646,6 +655,11 @@ module Leptris
646
655
  # read_owned_string (the seam frees it).
647
656
  attach_function :leptris_document_serialize_ext,
648
657
  [:leptris_document, :pointer, :pointer], :pointer
658
+ # Sized variant (1.9.19, upstream #644 — leptris-ruby PR #107):
659
+ # passing our ext-struct size keeps the engine from reading
660
+ # past this binding's allocation (the MSVC segfault).
661
+ attach_function :leptris_document_serialize_ext_sized,
662
+ [:leptris_document, :pointer, :pointer, :size_t], :pointer
649
663
  attach_function :leptris_document_serialize_into,
650
664
  [:leptris_document, :pointer, :size_t, :pointer, :pointer], :size_t
651
665
  attach_function :leptris_document_get_dtd,
@@ -766,6 +780,10 @@ module Leptris
766
780
  PULL_PI = 5
767
781
  PULL_END_DOCUMENT = 6
768
782
  PULL_ERROR = 7
783
+ # Prefix events (types.h LeptrisPullEventType 8/9): the
784
+ # default namespace's prefix is legitimately "".
785
+ PULL_START_PREFIX = 8
786
+ PULL_END_PREFIX = 9
769
787
 
770
788
  LEPTRIS_PARSE_DEFAULT = 0
771
789
  LEPTRIS_PARSE_DROP_WS_TEXT = 1
@@ -273,6 +273,31 @@ class Leptris::XML::Node
273
273
  end
274
274
  alias_method :remove, :unlink
275
275
 
276
+ # Visits the subtree with ONE C call (leptris_node_visit,
277
+ # libleptris 1.9.20 — upstream #645a): elements yield twice —
278
+ # (node, true, depth) before their children, (node, false, depth)
279
+ # after the subtree completes — every other kind once with
280
+ # entering=true; depth counts element levels from the receiver.
281
+ # No NodeSet, pointer array, or children memo per level: the
282
+ # leanest full-subtree iteration the binding offers (a document
283
+ # receiver walks the document child chain). The walk is read-only
284
+ # — mutate only between visits.
285
+ #
286
+ # root.visit { |node, entering, depth| ... }
287
+ def visit(&block)
288
+ return enum_for(:visit) unless block
289
+ ensure_alive!
290
+ document = @document
291
+ visitor = ::FFI::Function.new(
292
+ :void, [:pointer, :pointer, :int, :int], blocking: true) do |_, node_ptr, entering, depth|
293
+ block.call(
294
+ Leptris::XML::Node.wrap(node_ptr, document),
295
+ entering == 1, depth)
296
+ end
297
+ Leptris::XML::FFI.leptris_node_visit(@c_ptr, visitor, nil)
298
+ self
299
+ end
300
+
276
301
  # Walks the subtree in post-order DFS (matches Nokogiri's
277
302
  # semantics): the receiver, its descendants, nothing else.
278
303
  #
@@ -28,6 +28,13 @@ module Leptris::XML::Pull
28
28
  Leptris::XML::FFI::PULL_PI => :pi,
29
29
  Leptris::XML::FFI::PULL_END_DOCUMENT => :end_document,
30
30
  Leptris::XML::FFI::PULL_ERROR => :error,
31
+ # Prefix events ride the stream ("" prefix = the default
32
+ # namespace): the engine has always delivered them; the map
33
+ # lacked the kinds, silently yielding nil-typed Events — which
34
+ # the staging guard then misread as corruption after the real
35
+ # engine fix landed (leptris/leptris#648's residual).
36
+ Leptris::XML::FFI::PULL_START_PREFIX => :start_prefix,
37
+ Leptris::XML::FFI::PULL_END_PREFIX => :end_prefix,
31
38
  }.freeze
32
39
 
33
40
  class Parser
@@ -87,7 +94,7 @@ module Leptris::XML::Pull
87
94
  type = TYPES[buffer.get_int(base + type_off)]
88
95
  name = name_ptr.null? ? nil :
89
96
  name_ptr.read_string.force_encoding(Encoding::UTF_8)
90
- # Staging-corruption guard (leptris/leptris#646): the C
97
+ # Staging-corruption guard (leptris/leptris#648): the C
91
98
  # batch's staging arena misplaces record strings once the
92
99
  # staged content crosses its block boundary (~185 bytes of
93
100
  # attribute value under nested attr-carrying ancestors) —
@@ -97,10 +104,12 @@ module Leptris::XML::Pull
97
104
  # instead of delivering garbage, and point at the cursor
98
105
  # path, which reads the same documents correctly.
99
106
  if type.nil? ||
100
- (name && (name.empty? || !name.valid_encoding? ||
101
- name.match?(/[\x00-\x08\x0e-\x1f]/)))
107
+ (name && (!name.valid_encoding? ||
108
+ name.match?(/[\x00-\x08\x0e-\x1f]/))) ||
109
+ (%i[start_element end_element pi].include?(type) &&
110
+ (name.nil? || name.empty?))
102
111
  raise Leptris::XML::Error,
103
- "pull batch staging corruption (leptris/leptris#646) — " \
112
+ "pull batch staging corruption (leptris/leptris#648) — " \
104
113
  "use Parser#each (cursor) for this document"
105
114
  end
106
115
  text = text_ptr.null? ? nil :
@@ -47,8 +47,28 @@ module Leptris::XML::Serialization
47
47
  def self.to_xml_display(c_ptr, indent: 0, no_decl: false, encoding: nil)
48
48
  opts, encoding_anchor = build_options(
49
49
  indent: indent, no_decl: no_decl, encoding: encoding)
50
- str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext(
51
- c_ptr, opts.pointer, INDENT_TEXT_EXT.pointer)
50
+ str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext_sized(
51
+ c_ptr, opts.pointer, INDENT_TEXT_EXT.pointer, INDENT_TEXT_EXT.size)
52
+ Leptris::XML::FFI.read_owned_string(str_ptr)
53
+ end
54
+
55
+ # Indent-unit serialization (libleptris 1.9.22, #633 —
56
+ # leptris-ruby#109): Nokogiri's indent_text semantics — the unit
57
+ # string replaces the default spaces, repeated `indent` times per
58
+ # depth level, standard layout otherwise. The engine emits ONE
59
+ # copy of the unit string per level (options->indent is ignored
60
+ # when a unit is set), so the binding multiplies unit x indent to
61
+ # reach Nokogiri's repeat count.
62
+ def self.to_xml_indent_unit(c_ptr, unit, indent: 0, no_decl: false,
63
+ encoding: nil)
64
+ opts, encoding_anchor = build_options(
65
+ indent: indent, no_decl: no_decl, encoding: encoding)
66
+ ext = Leptris::XML::FFI::SerializeExtStruct.new
67
+ unit_anchor = ::FFI::MemoryPointer.from_string(
68
+ indent.positive? ? unit.to_s * indent : unit.to_s)
69
+ ext[:indent_unit] = unit_anchor
70
+ str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext_sized(
71
+ c_ptr, opts.pointer, ext.pointer, ext.size)
52
72
  Leptris::XML::FFI.read_owned_string(str_ptr)
53
73
  end
54
74
 
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.42
4
+ version: 1.9.44
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.