leptris 1.9.36 → 1.9.38

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: 31fdbaa742e583a50409dea1a5bda76e454e3b54ec30429d74101d5438f91b6f
4
- data.tar.gz: 6acd554905283d755febf5c6265ce2a97dd8f9ce1ef83c9804363ff2d5bf1000
3
+ metadata.gz: d6a20a13a634924317b339a27ecae165e6998c4d3de38854edd48d6ca68fa963
4
+ data.tar.gz: 87fdd1085af203af496cb625f02406073a000c1e5d6f1bcce046222fcc8d9a74
5
5
  SHA512:
6
- metadata.gz: 7e4c9abd84b07a678394774470b1486ccb2e03146578cda7eded0da2aa42f0b33f3ae682ac5a1881d2661e4a90ddb3bd6e0b3098d411303268867a2bbc1ad0d5
7
- data.tar.gz: 35a2088c31974f7dc19beaa0bd4ad5c73515be100b1c0d3b3fd3bc313fa10e7abc28202f3eb7bb3cce6035ad86b0c3f553bde0ca458e08a03110ba0d774ff07a
6
+ metadata.gz: c635bbd6bff75845204d2e34665003f9652b68dfe834f1484b5da84dd26e4db5992b14bb9b5d51e5cb03d9ee2b8e4ca64d6190aeb3fffa631011fbf9b1316625
7
+ data.tar.gz: fdef1ebde1f5770b0379242ae258ef8fcc1331353bc25f0ad283a6c13fea23d0df9508fb4623ffd0dffd798298b3b324efc7d92dae3131063436682fa6762d7c
data/CHANGELOG.md CHANGED
@@ -5,6 +5,58 @@ 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.38] - 2026-08-29
9
+
10
+ ### Changed
11
+
12
+ - **libleptris 1.9.13 lockstep**: pin bumped; audit 250/250, no
13
+ new C surface. Engine: document-level whitespace now chains as
14
+ TEXT children of the document node with libxml2's exact rule
15
+ (leading prolog ws dropped, ws after a prolog PI dropped, ws
16
+ after a comment or the root kept, trailing tail trimmed) — a
17
+ spec pins the rule through `Document#children`; XSLT suite
18
+ 180 -> 181/205.
19
+ - **Element-only SAX handlers ride the element batch** in the
20
+ DOM-backed dispatcher (round XXIII's path): handlers overriding
21
+ only element events skip the per-child get_type walk entirely —
22
+ text/comments/PIs are never fetched. The document node keeps the
23
+ typed walk (it is not an element handle).
24
+
25
+ ### Meta
26
+
27
+ - Nokogiri-compat audit against moxml's adapter surface: no gaps
28
+ (remove_attribute, add_namespace_definition, replace,
29
+ add_previous/next_sibling all present).
30
+
31
+ ## [1.9.37] - 2026-08-29
32
+
33
+ ### Added
34
+
35
+ - **`Element#inner_html`** (Nokogiri parity): the serialized
36
+ children — elements through the engine serializer, text
37
+ XML-escaped at the seam (`& < >` and CR → `&#xD;`, libxml2's
38
+ rules), comments/CDATA/PIs in literal forms. Well-formed by
39
+ construction: a spec re-parses the output to the same children.
40
+ (Nokogiri's inner_html HTML-serializes XML documents — SGML-style
41
+ PI closes, bare CDATA content — and its output does not
42
+ re-parse.) Measured 21 µs/element vs Nokogiri 15 — a first cut
43
+ on a new API with correct forms as the contract.
44
+ - **libleptris 1.9.12 lockstep** (with 1.9.11): pure XSLT
45
+ conformance upstream (libxslt suite 152 -> 180/205 — template
46
+ priority, xsl:number, attribute sets, namespace-declaration
47
+ ordering, strip/preserve-space, copy semantics); audit 250/250,
48
+ no new C surface.
49
+
50
+ ### Changed
51
+
52
+ - **Serialize byte-scratch**: the size+fill serialization pair
53
+ allocated and freed a MemoryPointer per call — inner_html
54
+ serializes each child, and the allocation dominated per-child
55
+ cost. A grow-only thread-local byte scratch (the pointer-scratch
56
+ discipline) now serves every serialize call. Serialized output
57
+ is also forced UTF-8 at the seam (previously true only by
58
+ accident of ASCII-only content comparisons).
59
+
8
60
  ## [1.9.36] - 2026-08-29
9
61
 
10
62
  ### 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.10"
11
+ LIBLEPTRIS_VERSION = "1.9.13"
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.36"
4
+ VERSION = "1.9.38"
5
5
  end
@@ -336,6 +336,28 @@ class Leptris::XML::Element < Leptris::XML::Node
336
336
  scopes
337
337
  end
338
338
 
339
+ # The serialized children (Nokogiri parity): elements serialize
340
+ # through the engine (correct escaping), text is XML-escaped,
341
+ # comments/CDATA/PIs render their literal forms. The complement
342
+ # of #inner_text, which returns the unescaped text content.
343
+ def inner_html
344
+ children.map do |child|
345
+ case child
346
+ when Leptris::XML::Element then child.to_xml
347
+ when Leptris::XML::CDATA # before Text: CDATA subclasses it
348
+ "<![CDATA[#{child.content}]]>"
349
+ when Leptris::XML::Text
350
+ Leptris::XML::Serialization.escape_text(child.content)
351
+ when Leptris::XML::Comment then "<!--#{child.content}-->"
352
+ when Leptris::XML::ProcessingInstruction
353
+ data = child.content
354
+ data.empty? ? "<?#{child.name}?>" : "<?#{child.name} #{data}?>"
355
+ else
356
+ Leptris::XML::Serialization.escape_text(child.content.to_s)
357
+ end
358
+ end.join
359
+ end
360
+
339
361
  def to_xml(indent: 0, no_decl: false, encoding: nil)
340
362
  Leptris::XML::Serialization.to_xml(
341
363
  Leptris::XML::FFI.method(:leptris_element_serialize_into), @c_ptr,
@@ -865,13 +865,24 @@ module Leptris
865
865
  def self.serialize_into_string(ffi_function, c_ptr, options)
866
866
  need = ffi_function.call(c_ptr, nil, 0, nil, options)
867
867
  return "" if need.zero?
868
- buffer = ::FFI::MemoryPointer.new(:char, need)
869
- begin
870
- ffi_function.call(c_ptr, buffer, need, nil, options)
871
- buffer.read_string
872
- ensure
873
- buffer.free
868
+ buffer = scratch_bytes(need)
869
+ ffi_function.call(c_ptr, buffer, need, nil, options)
870
+ buffer.read_string.force_encoding(Encoding::UTF_8)
871
+ end
872
+
873
+ # Byte scratch for serialize-into cycles: the size+fill pair
874
+ # allocated and freed a buffer per call — inner_html
875
+ # serializes each child, and the allocation was most of the
876
+ # per-child cost. Grows to the largest serialization seen;
877
+ # no Ruby reentrancy during a serialize call.
878
+ def self.scratch_bytes(need)
879
+ scratch = (Thread.current[:leptris_scratch] ||= {})
880
+ ptr = scratch[:bytes]
881
+ if ptr.nil? || ptr.size < need
882
+ ptr&.free
883
+ ptr = scratch[:bytes] = ::FFI::MemoryPointer.new(:char, need)
874
884
  end
885
+ ptr
875
886
  end
876
887
 
877
888
  # All-kind child handle batch (libleptris 1.7.0). Opportunistic
@@ -29,7 +29,10 @@ module Leptris::XML::SAX::DomDispatch
29
29
  def dispatch(handler, dispatched, doc)
30
30
  handler.start_document if dispatched[:start_document]
31
31
  doc_node = doc.node
32
- visit_children(doc_node.c_ptr, doc, handler, dispatched) if doc_node
32
+ if doc_node
33
+ visit_children(doc_node.c_ptr, doc, handler, dispatched,
34
+ element_parent: false)
35
+ end
33
36
  handler.end_document if dispatched[:end_document]
34
37
  self
35
38
  end
@@ -41,10 +44,23 @@ module Leptris::XML::SAX::DomDispatch
41
44
  CDATA = Leptris::XML::FFI::NODE_CDATA
42
45
  PI = Leptris::XML::FFI::NODE_PI
43
46
 
44
- def visit_children(c_ptr, doc, handler, dispatched)
47
+ def visit_children(c_ptr, doc, handler, dispatched, element_parent: true)
45
48
  wants_text_kinds = dispatched[:characters] ||
46
49
  dispatched[:comment] ||
47
50
  dispatched[:cdata] || dispatched[:pi]
51
+ if element_parent && !wants_text_kinds
52
+ # Element-only handlers under an element: the element batch
53
+ # hands out element pointers with no per-child get_type
54
+ # (round XXIII's path) — text/comments/PIs are never fetched.
55
+ # (The document node is not an element handle — the typed
56
+ # walk serves it.)
57
+ Leptris::XML::FFI.fetch_element_children(c_ptr).each do |child_ptr|
58
+ visit_element(
59
+ Leptris::XML::Node.wrap(child_ptr, doc, node_type: ELEMENT),
60
+ handler, dispatched)
61
+ end
62
+ return
63
+ end
48
64
  Leptris::XML::FFI.fetch_children(c_ptr).each do |child_ptr|
49
65
  case Leptris::XML::FFI.leptris_node_get_type(child_ptr)
50
66
  when ELEMENT
@@ -62,6 +62,19 @@ module Leptris::XML::Serialization
62
62
  Leptris::XML::FFI.read_owned_string(str_ptr)
63
63
  end
64
64
 
65
+ # XML-escapes text content for inner_html: & first, then < >
66
+ # and CR (libxml2 emits &#xD; for a bare carriage return).
67
+ ESCAPE_TEXT = {
68
+ "&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "\r" => "&#xD;",
69
+ }.freeze
70
+ private_constant :ESCAPE_TEXT
71
+ ESCAPE_TEXT_RE = /[&<>\r]/.freeze
72
+ private_constant :ESCAPE_TEXT_RE
73
+
74
+ def self.escape_text(string)
75
+ string.gsub(ESCAPE_TEXT_RE, ESCAPE_TEXT)
76
+ end
77
+
65
78
  # Builds a SerializeOptions struct. Returns [opts, encoding_anchor];
66
79
  # the anchor must stay referenced while opts is in an FFI call.
67
80
  def self.build_options(indent: 0, no_decl: false, encoding: nil)
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.36
4
+ version: 1.9.38
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-28 00:00:00.000000000 Z
11
+ date: 2026-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi