leptris 1.9.39 → 1.9.40

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: 21a0f9d2bf7f1c6758b6e2e31dcb66e0e5a11bf40580608cb02834cf80685785
4
- data.tar.gz: a7581350e1b5838a4d3d2c35e4ed6b73b3944709b9ff8e171dcdcc0d1d67181c
3
+ metadata.gz: 63ca61bcacf68aee02e3fb94c53f84d64af4474cf5b250482135a632b5ac2009
4
+ data.tar.gz: 269dd93a547a099a5eb0685f9d58ecfa2d281d373f969cc8e69a7cd6aece5614
5
5
  SHA512:
6
- metadata.gz: 5a824a978fbe93bee62dd9cf760305e320dbf0c537a764be413368f2ff77203c777b33dc9e27fb09c6252e0583084e1b4488d91050a47204ce0c6963b01a80cc
7
- data.tar.gz: e96ca075b15efc6dccc627ee01413cc4fb98be27ab351599e7e1348a1b833023fdc2ce04cae5fedf8ccd1b26b0066c140b831f89ccf9cc7e044ff988cd3d89ca
6
+ metadata.gz: d908e6a85d409bae57e50703a87be059e984a67af44139769475f6baa82631894d9cbd26390442f32af54d2987242fee8bc528f70f52e0284692fd522cb2ea23
7
+ data.tar.gz: 9826c4922df24a0703eca6e39b8b6f62fe5c6deccdf5212b7e8bcc76897b74ce4cc587add6a95aa60324dcff538087cdc5bb5ba35e421a37ce0bc26a18237bad
data/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ 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.40] - 2026-08-30
9
+
10
+ ### Changed
11
+
12
+ - **SAX delivers through the engine transports again** — the #95
13
+ attribute corruption is fixed upstream (libleptris 1.9.18,
14
+ verified on the issue fixture through both engine transports:
15
+ all 7 image attributes intact). `SAX::Parser` defaults to
16
+ `streaming: true`; `streaming: false` keeps the DOM-backed
17
+ dispatcher as a fallback for older engines. The correctness tax
18
+ is gone: all-events 119 ms and text-only 21.6 ms per big.xml
19
+ parse vs Nokogiri's 131/130 ms — SAX wins every shape outright
20
+ again, with correct data.
21
+ - **libleptris 1.9.14–1.9.18 lockstep** (audit 253/253, three new
22
+ symbols): fixes for everything we were tracking upstream —
23
+ #625 (SAX attrs), #626 (MSVC non-ASCII names; the Windows spec
24
+ gate is removed), #627/#628/#630 (XSLT unknown fns and last();
25
+ relative namespaced descendant paths — spec added), #613
26
+ (latin-1), #608 (combined ns+vars eval; attached), #617
27
+ (node_children out_kinds; wired), #633 (pretty-print parity),
28
+ #635 (xmlns interleave; wired).
29
+ - **Children batches carry kinds** (`leptris_node_children_ex`):
30
+ `#children` and the fragment/dispatch walks wrap with the kind
31
+ hint — no per-child get_type dispatch. Measured: the cold
32
+ full-tree walk's remaining gap vs Nokogiri (1.5x) is the Ruby
33
+ wrapper allocation floor, not type dispatch; noted for the
34
+ record.
35
+ - **Exact xmlns interleave** (`leptris_element_attributes_raw`):
36
+ the DOM-backed fallback now reproduces the streaming contract's
37
+ byte-positioned declarations; the #99 spec is upgraded from
38
+ set-equality to exact order equality.
39
+
8
40
  ## [1.9.39] - 2026-08-29
9
41
 
10
42
  ### 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.13"
11
+ LIBLEPTRIS_VERSION = "1.9.18"
12
12
 
13
13
  CMAKE_FLAGS = %w[
14
14
  -DCMAKE_BUILD_TYPE=Release
@@ -30,7 +30,17 @@ task :compile do
30
30
  mkdir_p(build)
31
31
  url = "https://api.github.com/repos/leptris/leptris/tarball/v#{version}"
32
32
  sh "curl -sL #{url} | tar xz -C #{build} --strip-components=1"
33
- sh "cmake -B #{build}/build -S #{build} #{CMAKE_FLAGS.join(' ')}"
33
+ # libleptris 1.9.18's xslt_functions.c:270 assigns LeptrisElement
34
+ # to LeptrisNodeRef — GCC 14 (Alpine/musl) makes incompatible
35
+ # pointer types an error by default and the musl platform gems
36
+ # fail to build; clang toolchains only warn. Downgrade to warning
37
+ # until the upstream fix (leptris/leptris#640).
38
+ # No quoting: the flag carries no spaces, and single quotes are
39
+ # literal on Windows shells (they broke the MSVC build).
40
+ # GCC-family only — MSVC's cl rejects the flag outright (D8021).
41
+ cflags = Gem.win_platform? ? "" : "-Wno-error=incompatible-pointer-types"
42
+ sh "cmake -B #{build}/build -S #{build} " \
43
+ "#{CMAKE_FLAGS.join(' ')} #{cflags.empty? ? '' : "-DCMAKE_C_FLAGS=#{cflags}"}"
34
44
  sh "cmake --build #{build}/build --config Release -j 4"
35
45
  # Windows names the shared library leptris.dll (no "lib" prefix);
36
46
  # vendoring under the uniform libleptris.* name keeps the FFI
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.39"
4
+ VERSION = "1.9.40"
5
5
  end
@@ -25,8 +25,9 @@ class Leptris::XML::DocumentFragment
25
25
  end
26
26
 
27
27
  def children
28
- nodes = Leptris::XML::FFI.fetch_children(@c_ptr).map do |ptr|
29
- Leptris::XML::Node.wrap(ptr, @document)
28
+ pointers, kinds = Leptris::XML::FFI.fetch_children(@c_ptr)
29
+ nodes = Array.new(pointers.size) do |i|
30
+ Leptris::XML::Node.wrap(pointers[i], @document, node_type: kinds[i])
30
31
  end
31
32
  Leptris::XML::NodeSet.new(@document, nodes)
32
33
  end
@@ -225,8 +225,17 @@ module Leptris
225
225
  # count-only query returning the TOTAL across every child kind.
226
226
  attach_function :leptris_node_children,
227
227
  [:leptris_node_ref, :pointer, :size_t], :size_t
228
+ # Children with out_kinds (1.9.18, upstream #617): the kind
229
+ # rides the batch, so wrappers skip the per-child get_type.
230
+ attach_function :leptris_node_children_ex,
231
+ [:leptris_node_ref, :pointer, :pointer, :size_t], :size_t
228
232
  # Element-only batch; mirror-only — fetch_children rides the
229
233
  # all-kind node_children above.
234
+ # Raw qname-ordered attribute list INCLUDING xmlns
235
+ # declarations at their byte positions (1.9.18, upstream
236
+ # #635) — the streaming SAX contract's mixed view.
237
+ attach_function :leptris_element_attributes_raw,
238
+ [:leptris_element, :pointer, :pointer, :size_t], :size_t
230
239
  attach_function :leptris_element_children,
231
240
  [:leptris_element, :pointer, :size_t], :size_t
232
241
  attach_function :leptris_node_as_element,
@@ -538,6 +547,11 @@ module Leptris
538
547
  attach_function :leptris_xpath_compiled_eval_vars,
539
548
  [:leptris_xpath_compiled, :leptris_document, :leptris_element,
540
549
  :leptris_xpath_var_set], :leptris_xpath_result
550
+ # Combined namespaces + variables (1.9.18, upstream #608).
551
+ attach_function :leptris_xpath_compiled_eval_ns_vars,
552
+ [:leptris_xpath_compiled, :leptris_document, :leptris_element,
553
+ :leptris_xpath_ns_set, :leptris_xpath_var_set],
554
+ :leptris_xpath_result
541
555
  # Source text of a compiled expression (owned by the handle).
542
556
  attach_function :leptris_xpath_compiled_text,
543
557
  [:leptris_xpath_compiled], :string
@@ -893,6 +907,9 @@ module Leptris
893
907
  FETCH_INITIAL = 32
894
908
  private_constant :FETCH_INITIAL
895
909
 
910
+ # Returns [pointers, kinds] — the kinds ride the batch
911
+ # (leptris_node_children_ex, 1.9.18 #617) so wrappers skip
912
+ # the per-child get_type dispatch.
896
913
  def self.fetch_children(c_ptr)
897
914
  scratch = (Thread.current[:leptris_scratch] ||= {})
898
915
  buffer = scratch[:pointers]
@@ -900,18 +917,57 @@ module Leptris
900
917
  buffer = scratch[:pointers] =
901
918
  ::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL)
902
919
  end
920
+ # size the kinds buffer from the POINTERS buffer's capacity —
921
+ # it may have grown past FETCH_INITIAL on earlier use, and C
922
+ # writes one kind per copied child
903
923
  cap = buffer.size / PTR_BYTES
904
- copied = leptris_node_children(c_ptr, buffer, cap)
924
+ kinds = scratch_ints(cap)
925
+ copied = leptris_node_children_ex(c_ptr, buffer, kinds, cap)
905
926
  if copied == cap
906
- total = leptris_node_children(c_ptr, nil, 0)
927
+ total = leptris_node_children_ex(c_ptr, nil, nil, 0)
907
928
  if total > cap
908
929
  buffer.free
909
930
  buffer = scratch[:pointers] =
910
931
  ::FFI::MemoryPointer.new(:pointer, total)
911
- copied = leptris_node_children(c_ptr, buffer, total)
932
+ kinds = scratch_ints(total)
933
+ copied = leptris_node_children_ex(c_ptr, buffer, kinds, total)
912
934
  end
913
935
  end
914
- buffer.get_array_of_pointer(0, copied)
936
+ [buffer.get_array_of_pointer(0, copied),
937
+ kinds.get_array_of_int(0, copied)]
938
+ end
939
+
940
+ # Raw qname-ordered attribute pairs INCLUDING xmlns at their
941
+ # byte positions (leptris_element_attributes_raw, 1.9.18
942
+ # #635) — the streaming SAX contract's mixed view.
943
+ def self.fetch_attributes_raw(el_ptr)
944
+ total = leptris_element_attributes_raw(el_ptr, nil, nil, 0)
945
+ return [] if total.zero?
946
+ scratch = (Thread.current[:leptris_scratch] ||= {})
947
+ names = scratch[:attr_names]
948
+ if names.nil? || names.size / PTR_BYTES < total
949
+ names&.free
950
+ names = scratch[:attr_names] =
951
+ ::FFI::MemoryPointer.new(:pointer, total)
952
+ end
953
+ values = scratch[:attr_values]
954
+ if values.nil? || values.size / PTR_BYTES < total
955
+ values&.free
956
+ values = scratch[:attr_values] =
957
+ ::FFI::MemoryPointer.new(:pointer, total)
958
+ end
959
+ copied = leptris_element_attributes_raw(
960
+ el_ptr, names, values, total)
961
+ Array.new(copied) do |i|
962
+ [utf8(names.get_pointer(i * PTR_BYTES).read_string),
963
+ utf8(values.get_pointer(i * PTR_BYTES).read_string)]
964
+ end
965
+ end
966
+
967
+ # Attribute strings cross the seam UTF-8 on both faces
968
+ # (ADR-0002).
969
+ def self.utf8(str)
970
+ str.nil? ? nil : str.force_encoding(Encoding::UTF_8)
915
971
  end
916
972
 
917
973
  # Element-only child batch (the mirror of fetch_children over
@@ -156,12 +156,15 @@ class Leptris::XML::Node
156
156
 
157
157
  def children
158
158
  # Immutable in readonly mode: the batch fetch plus wrapper
159
- # construction is paid once.
159
+ # construction is paid once. The kinds ride the batch
160
+ # (leptris_node_children_ex), so no per-child get_type.
160
161
  return @children if memo_hit?(@children_version)
161
162
  ensure_alive!
162
163
  parent = as_element_or_self
163
- nodes = Leptris::XML::FFI.fetch_children(@c_ptr).map do |ptr|
164
- Leptris::XML::Node.wrap(ptr, @document, parent: parent)
164
+ pointers, kinds = Leptris::XML::FFI.fetch_children(@c_ptr)
165
+ nodes = Array.new(pointers.size) do |i|
166
+ Leptris::XML::Node.wrap(pointers[i], @document,
167
+ parent: parent, node_type: kinds[i])
165
168
  end
166
169
  result = Leptris::XML::NodeSet.new(@document, nodes)
167
170
  if @document
@@ -61,8 +61,9 @@ module Leptris::XML::SAX::DomDispatch
61
61
  end
62
62
  return
63
63
  end
64
- Leptris::XML::FFI.fetch_children(c_ptr).each do |child_ptr|
65
- case Leptris::XML::FFI.leptris_node_get_type(child_ptr)
64
+ pointers, kinds = Leptris::XML::FFI.fetch_children(c_ptr)
65
+ pointers.each_with_index do |child_ptr, i|
66
+ case kinds[i]
66
67
  when ELEMENT
67
68
  visit_element(
68
69
  Leptris::XML::Node.wrap(child_ptr, doc, node_type: ELEMENT),
@@ -115,19 +116,12 @@ module Leptris::XML::SAX::DomDispatch
115
116
 
116
117
  # start_element's pairs with xmlns declarations carried — the
117
118
  # engine's streaming contract reports declarations among the
118
- # attribute pairs (canon-style namespace-aware consumers split
119
- # them). The DOM model stores declarations and attributes
120
- # separately, so the exact byte-level interleave is unrecoverable
121
- # here: declarations emit first in declaration order (default as
122
- # "xmlns", prefixed as "xmlns:prefix"), then the attributes in
123
- # source order (leptris-ruby#99).
119
+ # attribute pairs at their byte positions. Since libleptris
120
+ # 1.9.18 (upstream #635, closing leptris-ruby#99's documented
121
+ # difference), the raw qname-ordered list reproduces the exact
122
+ # interleave.
124
123
  def declared_pairs(element)
125
- defs = element.namespace_definitions
126
- return attr_pairs(element.c_ptr) if defs.empty?
127
- pairs = defs.map do |ns|
128
- [ns.prefix ? "xmlns:#{ns.prefix}" : "xmlns", ns.href]
129
- end
130
- pairs.concat(attr_pairs(element.c_ptr))
124
+ Leptris::XML::FFI.fetch_attributes_raw(element.c_ptr)
131
125
  end
132
126
 
133
127
  # [name, value] pairs in source order, read straight from the
@@ -8,15 +8,15 @@ class Leptris::XML::SAX::Parser
8
8
 
9
9
  attr_reader :document, :encoding
10
10
 
11
- # streaming: false (default) delivers from a DOM parse —
12
- # leptris-ruby#95: the engine's streaming SAX transports corrupt
13
- # attribute state on nested attribute-heavy shapes while the DOM
14
- # parser reads the same bytes correctly. streaming: true opts
15
- # back into the engine transports (bulk/callback by override
16
- # weight) once the engine fix lands, or for consumers that
17
- # accept the risk.
11
+ # streaming: true (default, since 1.9.40) delivers through the
12
+ # engine transports bulk/callback picked by override weight
13
+ # (leptris-ruby#95's attribute corruption is fixed upstream in
14
+ # libleptris 1.9.18, verified on the issue fixture). streaming:
15
+ # false delivers from a DOM parse (SAX::DomDispatch) the
16
+ # correctness-first fallback kept for engines older than 1.9.18
17
+ # and for consumers wanting the DOM view's shapes.
18
18
  def initialize(handler = Leptris::XML::SAX::Document.new,
19
- encoding = nil, streaming: false)
19
+ encoding = nil, streaming: true)
20
20
  @document = handler
21
21
  @encoding = encoding
22
22
  @streaming = streaming
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.39
4
+ version: 1.9.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-29 00:00:00.000000000 Z
11
+ date: 2026-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi