leptris 1.9.16-aarch64-linux → 1.9.19-aarch64-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: f6960184588bed5ebd9fa8fbf64fe97bb8476b433ba43b5176568f20b9dd561b
4
- data.tar.gz: 739e787fbd3c921bcb2ac4cdf776b3f85bc7ec2fe870b8f94f9366cd3aacc710
3
+ metadata.gz: c17a17ac9f4c53847365c8e9eedfaa025713970e9d7831d3a5ada65941bbcffe
4
+ data.tar.gz: 889a3d367d1fed900d5ae12d0afaa12b4fe7f4125f8c32be11cba127460e2e8a
5
5
  SHA512:
6
- metadata.gz: 2588d661f45aec8859681b381fffd2fb05b4600f2888c826ad86a1c43f8ad86d7a8fc95b8ce4d5ae803772275066e4f43e67f09bf0592d5648d732383081168e
7
- data.tar.gz: d01eb46bdd19bbdfdf26c6fb278111291f8713439d088f205f7985ba46f31696c344c864f8d95229d4e389c7bc857bdfaae25721cf49d8475c71936c2344b8e1
6
+ metadata.gz: 59f1415e49d63285c56dac59891d83c08364146b40d221c05255cb141fe57dd23314288fb9ab997620fe3b3e0e7ae75d3db78b078ab0f6cbfe8aeab706c7f6da
7
+ data.tar.gz: 44d6c18f3866c5bdc769b095ebe1ef62c1aa7a47956c892e3562b5756b9d50191b4b516c88a33ebb75a642356ab984e8a8cc6588c36c6f68b2eec15b9baba80b
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.19] - 2026-08-27
9
+
10
+ Lockstep with libleptris 1.9.3 — unblocks the 1.9.18 release
11
+ (v1.9.2 did not compile on musl, upstream #582).
12
+
13
+ ### Added
14
+
15
+ - **`Document#comments`**: document-level comments — parsed
16
+ `<!-- ... -->` outside the root, prolog then epilog, in document
17
+ order (upstream #578). UTF-8, version-memoized like the PI list;
18
+ the companion reader to `#processing_instructions`.
19
+
20
+ ### Fixed
21
+
22
+ - Rides the engine's v1.9.3 fixes: dataless PIs parse correctly
23
+ (#577 — `<?pi?>` no longer clobbers its closing `?`), epilog
24
+ comments are kept and serialize after the root (#578), and
25
+ attribute values normalize per XML 1.0 §3.3.3 (#576 — whitespace
26
+ collapsing and entity decoding in attribute values).
27
+
28
+ ## [1.9.18] - 2026-08-27
29
+
30
+ Lockstep with libleptris 1.9.2 (covers 1.9.1 — the XSLT engine — and
31
+ 1.9.2, the options-struct ABI freeze; the binding's three-field
32
+ SerializeOptions layout is the frozen shape, so no binding change was
33
+ required for the fix).
34
+
35
+ ### Added
36
+
37
+ - **`Leptris::XML::XSLT` — XSLT 1.0 transforms**: the engine compiles
38
+ a stylesheet ONCE into an immutable instruction forest, then applies
39
+ it to any number of documents. `XSLT.parse(xml)` /
40
+ `XSLT.parse_file(path)` (resolves §2.7 embedded stylesheets);
41
+ `Stylesheet#apply_to(doc)` returns the result as an owning,
42
+ queryable Document; `Stylesheet#serialize(doc)` keeps top-level
43
+ text nodes and result fragments. Bad select expressions are
44
+ rejected at COMPILE time. The custom XPath-function handler seam
45
+ attaches mirror-only until a use case arrives.
46
+
47
+ ## [1.9.17] - 2026-08-27
48
+
49
+ ### Changed
50
+
51
+ - **Element-hinted batch materialization**: the XPath result-set
52
+ batch (`leptris_xpath_result_get_nodes_ex`) fills an out_kinds
53
+ array the binding discarded; `Node.wrap` now accepts a known
54
+ `node_type:` so element entries skip the per-node get_type
55
+ dispatch. ELEMENT is the only hintable value — XPath's data
56
+ model reports CDATA as TEXT, so a TEXT hint cannot distinguish
57
+ Text from CDATA (different content getters); comments/CDATA/PIs
58
+ keep the get_type fallback. Cold 500-element materialization:
59
+ 0.131 s -> 0.110 s (~15%). Upstream note: a full-type out_kinds
60
+ (or kinds on `leptris_node_children`) belongs to the #560/#562
61
+ ask family.
62
+
8
63
  ## [1.9.16] - 2026-08-27
9
64
 
10
65
  ### Changed
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.0"
11
+ LIBLEPTRIS_VERSION = "1.9.3"
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.16"
4
+ VERSION = "1.9.19"
5
5
  end
@@ -311,6 +311,21 @@ class Leptris::XML::Document
311
311
  @freed.state == :freed || @c_ptr.nil?
312
312
  end
313
313
 
314
+ # Document-level comments — parsed <!-- ... --> outside the
315
+ # root element, prolog then epilog, in document order (the
316
+ # companion reader to #processing_instructions; libleptris
317
+ # 1.9.3, upstream #578). Version-memoized like the PI list.
318
+ def comments
319
+ return @comments if @comments_version == @version
320
+ count = Leptris::XML::FFI.leptris_document_comment_count(@c_ptr)
321
+ result = Array.new(count) do |i|
322
+ Leptris::XML::FFI.leptris_document_comment_content(@c_ptr, i)
323
+ end
324
+ @comments = result
325
+ @comments_version = @version
326
+ result
327
+ end
328
+
314
329
  # The thread-global last-failure [line, column] (1-based), or nil
315
330
  # when no error is recorded — the position companion to
316
331
  # Document#last_error; populated by recover parses.
@@ -42,6 +42,7 @@ module Leptris
42
42
  typedef :pointer, :leptris_xpath_result
43
43
  typedef :pointer, :leptris_xpath_ns_set
44
44
  typedef :pointer, :leptris_xpath_compiled
45
+ typedef :pointer, :leptris_xslt
45
46
  typedef :pointer, :leptris_pull_parser
46
47
  typedef :pointer, :leptris_iterparse
47
48
  typedef :pointer, :leptris_xpath_var_set
@@ -124,6 +125,13 @@ module Leptris
124
125
  [:leptris_document, :size_t], :string
125
126
  attach_function :leptris_document_add_pi,
126
127
  [:leptris_document, :string, :string], :pointer
128
+ # Document-level comments (libleptris 1.9.3, #578): parsed
129
+ # <!-- ... --> outside the root, prolog then epilog, in
130
+ # document order. Read-only surface (no add_comment yet).
131
+ attach_function :leptris_document_comment_count,
132
+ [:leptris_document], :size_t
133
+ attach_function :leptris_document_comment_content,
134
+ [:leptris_document, :size_t], :string
127
135
  # Options-struct parse (v1.6.0): supersedes the flags variants;
128
136
  # the flags path remains bound for compatibility.
129
137
  attach_function :leptris_parse_string_ex,
@@ -478,7 +486,11 @@ module Leptris
478
486
  attach_function :leptris_xpath_compiled_eval_vars,
479
487
  [:leptris_xpath_compiled, :leptris_document, :leptris_element,
480
488
  :leptris_xpath_var_set], :leptris_xpath_result
489
+ # Source text of a compiled expression (owned by the handle).
490
+ attach_function :leptris_xpath_compiled_text,
491
+ [:leptris_xpath_compiled], :string
481
492
  attach_function :leptris_xpath_compiled_free,
493
+
482
494
  [:leptris_xpath_compiled], :void
483
495
 
484
496
  attach_function :leptris_sax_parse,
@@ -539,7 +551,23 @@ module Leptris
539
551
  [:leptris_element, :pointer, :size_t, :pointer, :pointer], :size_t
540
552
  attach_function :leptris_document_save_file,
541
553
  [:leptris_document, :string, :pointer], :leptris_status
554
+ # XSLT 1.0 engine (libleptris 1.9.1): compile once, apply many.
555
+ attach_function :leptris_xslt_parse,
556
+ [:string, :size_t], :leptris_xslt
557
+ attach_function :leptris_xslt_parse_file,
558
+ [:string], :leptris_xslt
559
+ attach_function :leptris_xslt_free,
560
+ [:leptris_xslt], :void
561
+ # Result tree is a document (free with document_free); errors
562
+ # surface via document_last_error on NULL.
563
+ attach_function :leptris_xslt_apply,
564
+ [:leptris_xslt, :leptris_document], :leptris_document
565
+ # apply + serialize in one call, keeping top-level text nodes
566
+ # and result fragments the tree API would flatten.
567
+ attach_function :leptris_xslt_apply_string,
568
+ [:leptris_xslt, :leptris_document], :pointer
542
569
  attach_function :leptris_c14n_canonicalize,
570
+
543
571
  [:leptris_document, :int, :int], :pointer
544
572
  attach_function :leptris_c14n_canonicalize_subtree,
545
573
  [:leptris_element, :int, :int], :pointer
@@ -648,6 +676,7 @@ module Leptris
648
676
  leptris_status_string leptris_last_error
649
677
  leptris_document_last_error leptris_document_encoding
650
678
  leptris_document_pi_target leptris_document_pi_data
679
+ leptris_document_comment_content
651
680
  leptris_element_name leptris_element_text leptris_element_prefix
652
681
  leptris_element_namespace
653
682
  leptris_element_attribute leptris_element_attribute_ns
@@ -747,14 +776,27 @@ module Leptris
747
776
  end
748
777
 
749
778
  # XPath result-set batch (leptris_xpath_result_get_nodes_ex):
750
- # copies all node kinds into a caller buffer and hands back
751
- # the pointer array.
779
+ # the call fills out_kinds in the 4-value XPATH_NODE space
780
+ # (ELEMENT/ATTRIBUTE/TEXT/OTHER). ELEMENT is the only value
781
+ # that maps unambiguously — XPath's data model reports CDATA
782
+ # as TEXT, so a TEXT hint cannot distinguish Text from CDATA
783
+ # (different content getters) — everything else falls back to
784
+ # get_type in the wrapper. Returns [pointers, element_hints].
785
+ XPATH_KIND_HINT = { XPATH_NODE_ELEMENT => NODE_ELEMENT }.freeze
786
+
752
787
  def self.fetch_result_nodes(result_ptr, count)
753
- return [] if count.zero?
788
+ return [[], nil] if count.zero?
754
789
  with_pointer_buffer(count) do |buffer|
755
- copied = leptris_xpath_result_get_nodes_ex(
756
- result_ptr, buffer, nil, count)
757
- buffer.get_array_of_pointer(0, copied)
790
+ kinds = ::FFI::MemoryPointer.new(:int, count)
791
+ begin
792
+ copied = leptris_xpath_result_get_nodes_ex(
793
+ result_ptr, buffer, kinds, count)
794
+ hints = kinds.get_array_of_int(0, copied)
795
+ .map { |k| XPATH_KIND_HINT[k] }
796
+ [buffer.get_array_of_pointer(0, copied), hints]
797
+ ensure
798
+ kinds.free
799
+ end
758
800
  end
759
801
  end
760
802
 
@@ -12,7 +12,10 @@ class Leptris::XML::Node
12
12
  @node_type = node_type
13
13
  end
14
14
 
15
- def self.wrap(c_ptr, document, parent: nil)
15
+ # node_type: callers holding a batch-fetched kind (the XPath
16
+ # result-set batch fills out_kinds) pass it so the wrap skips the
17
+ # get_type dispatch; nil (the default) dispatches as before.
18
+ def self.wrap(c_ptr, document, parent: nil, node_type: nil)
16
19
  # Per-document weak-ref cache. Returns the existing wrapper when the
17
20
  # same c_ptr is wrapped twice (common in children/sibling walks,
18
21
  # repeated xpath queries, traverse-then-access patterns). The cache
@@ -21,7 +24,7 @@ class Leptris::XML::Node
21
24
  return cached
22
25
  end
23
26
 
24
- node_type = Leptris::XML::FFI.leptris_node_get_type(c_ptr)
27
+ node_type ||= Leptris::XML::FFI.leptris_node_get_type(c_ptr)
25
28
  node =
26
29
  case node_type
27
30
  when Leptris::XML::FFI::NODE_ELEMENT
@@ -81,11 +81,11 @@ class Leptris::XML::NodeSet
81
81
  # not just elements); the batch accessor under-copies
82
82
  # mixed-kind results (upstream leptris#477), so per-index
83
83
  # fetch covers the remainder.
84
- pointers = Leptris::XML::FFI.fetch_result_nodes(@result_ptr, n)
84
+ pointers, kinds = Leptris::XML::FFI.fetch_result_nodes(@result_ptr, n)
85
85
  nodes = []
86
- pointers.each do |ptr|
86
+ pointers.each_with_index do |ptr, i|
87
87
  next if ptr.null?
88
- nodes << Leptris::XML::Node.wrap(ptr, @document)
88
+ nodes << Leptris::XML::Node.wrap(ptr, @document, node_type: kinds[i])
89
89
  end
90
90
  (pointers.length...n).each do |i|
91
91
  ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(@result_ptr, i)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module Leptris::XML::XSLT
6
+ module_function
7
+
8
+ def parse(stylesheet_xml)
9
+ Stylesheet.parse(stylesheet_xml)
10
+ end
11
+
12
+ def parse_file(path)
13
+ Stylesheet.parse_file(path)
14
+ end
15
+
16
+ # A compiled XSLT 1.0 stylesheet (libleptris 1.9.1): the stylesheet
17
+ # parses ONCE into an immutable instruction forest — patterns to
18
+ # XPath ASTs, selects through the compiled-XPath path — then applies
19
+ # to any number of documents with no re-parsing.
20
+ #
21
+ # style = Leptris::XML::XSLT.parse(<<~XSL)
22
+ # <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
23
+ # <xsl:template match="/"><out><xsl:value-of select="count(//item)"/></out></xsl:template>
24
+ # </xsl:stylesheet>
25
+ # XSL
26
+ # style.apply_to(doc) # => result Document (queryable tree)
27
+ # style.serialize(doc) # => String (fragments and top-level
28
+ # # text nodes preserved)
29
+ #
30
+ class Stylesheet
31
+ # GC-managed compiled handle.
32
+ class Handle < ::FFI::AutoPointer
33
+ def self.release(ptr)
34
+ Leptris::XML::FFI.leptris_xslt_free(ptr)
35
+ end
36
+ end
37
+
38
+ # Compile a stylesheet. Raises XPathError on a stylesheet syntax
39
+ # error (detail on the thread-global last error).
40
+ def self.parse(stylesheet_xml)
41
+ raw = Leptris::XML::FFI.leptris_xslt_parse(
42
+ stylesheet_xml.to_s, stylesheet_xml.to_s.bytesize)
43
+ if raw.null?
44
+ raise Leptris::XML::XPathError,
45
+ "stylesheet parse failed: #{Leptris::XML::FFI.leptris_last_error}"
46
+ end
47
+ new(Handle.new(raw))
48
+ end
49
+
50
+ # Compile from a file. Resolves §2.7 embedded stylesheets
51
+ # (xml-stylesheet PI with an href="#id" fragment) when the file's
52
+ # root is not itself a stylesheet.
53
+ def self.parse_file(path)
54
+ raw = Leptris::XML::FFI.leptris_xslt_parse_file(path.to_s)
55
+ if raw.null?
56
+ raise Leptris::XML::XPathError,
57
+ "stylesheet parse failed: #{Leptris::XML::FFI.leptris_last_error}"
58
+ end
59
+ new(Handle.new(raw))
60
+ end
61
+
62
+ def initialize(handle)
63
+ @handle = handle
64
+ end
65
+
66
+ # Apply to +document+ (not modified) and return the result tree
67
+ # as an owning Document — query it with xpath/css like any other.
68
+ def apply_to(document)
69
+ raw = Leptris::XML::FFI.leptris_xslt_apply(@handle, document.c_ptr)
70
+ if raw.null?
71
+ raise Leptris::XML::XPathError,
72
+ "transform failed: #{document.last_error || Leptris::XML::FFI.leptris_last_error}"
73
+ end
74
+ Leptris::XML::Document.wrap(raw)
75
+ end
76
+
77
+ # Apply and serialize in one call — keeps top-level text nodes and
78
+ # result fragments that the tree API would flatten.
79
+ def serialize(document)
80
+ str_ptr = Leptris::XML::FFI.leptris_xslt_apply_string(
81
+ @handle, document.c_ptr)
82
+ Leptris::XML::FFI.read_owned_string(str_ptr)
83
+ end
84
+ end
85
+ end
data/lib/leptris/xml.rb CHANGED
@@ -22,6 +22,7 @@ module Leptris
22
22
  autoload :Serialization, "leptris/xml/serialization"
23
23
  autoload :SAX, "leptris/xml/sax"
24
24
  autoload :XPath, "leptris/xml/xpath"
25
+ autoload :XSLT, "leptris/xml/xslt"
25
26
  autoload :Pull, "leptris/xml/pull"
26
27
  autoload :Iterparse, "leptris/xml/iterparse"
27
28
 
data/lib/libleptris.so CHANGED
Binary file
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.16
4
+ version: 1.9.19
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.
@@ -103,6 +103,7 @@ files:
103
103
  - lib/leptris/xml/serialization.rb
104
104
  - lib/leptris/xml/text.rb
105
105
  - lib/leptris/xml/xpath.rb
106
+ - lib/leptris/xml/xslt.rb
106
107
  - lib/libleptris.so
107
108
  homepage: https://github.com/leptris/leptris-ruby
108
109
  licenses: