leptris 1.9.53-x86_64-linux → 1.9.55-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: d9ea2c66283a8f4b985f250d908e407098c78db6582e6747376ceba6ccbb2c7a
4
- data.tar.gz: 6040c9e9b2b3b76f2e098005294d5d149122354f8d3fa8958f30c390341fe711
3
+ metadata.gz: 166f46bd7874e3160fbe3131115c32e1695354d5d503fc6684076d6723a8f4f3
4
+ data.tar.gz: e203358c5847147d46de10c52950d9b3a81cd4e3610b4b59ce6e4a396e7b79ff
5
5
  SHA512:
6
- metadata.gz: 16b683f6eceb1f573f951c6d68862fd431abac982a97e24104c0e14d7cf85d223916c08d663983f69f639f155062d22bb4ee8f08df0e04bd365598ffcab62c59
7
- data.tar.gz: 4e4e56d4037cc817c6071e215ab127d0d8302827872c3c4c1c059f9732c8bbd8904e4298588ae1b74fdb4b81172bc50c3c75957a9280324571a409b32b95d8e4
6
+ metadata.gz: ccf26a2227f5fcdca9a639f676a2388b58c294c710795662522264a5ded8c07e6adcbb188133ed12b92bc766fc00b1c54d6726456d057c9d21b1b8f5797765fd
7
+ data.tar.gz: 9730c0d093216b2f96befd533a5360af1fe0124337b373392f77509cc4ccef6538b72b787ae7da1967b86f4dacdd2c261b65b6f3e87f987eaadf16392298ebe6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,88 @@ 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.55] - 2026-09-02
9
+
10
+ ### Changed
11
+
12
+ - **Lockstep with libleptris 1.9.50**: XPath 2.0 type operators —
13
+ `instance of`, `castable as`, `cast as`, `treat as` with
14
+ SequenceType v1 (`node()`, `item()`, occurrence `*`/`+`) —
15
+ resolving standalone from `#xpath`, specced against the Saxon
16
+ ground truth including the falsifiable negative
17
+ (`'x' castable as xs:integer` = false, `1.9 cast as xs:integer`
18
+ = 1). No new C surface (audit 255/255).
19
+ - **README**: the standalone-subset example block grows the type
20
+ operators; the #683 gap list drops sequence types (still out:
21
+ value comparators, quantifiers, `intersect`/`except`, string
22
+ templates, XQuery).
23
+
24
+ ### Improved
25
+
26
+ - **Serialize + inner_html, one dispatch instead of two**: the
27
+ `_serialize_into` contract is snprintf-like (the return is
28
+ always the bytes needed incl. NUL; the copy happens only when
29
+ the buffer fits), so `serialize_into_string` now fills the
30
+ grow-only byte scratch opportunistically and probes the size
31
+ only when a child outgrows it — previously every call paid a
32
+ size-probe dispatch before the fill. The bound serializer
33
+ methods are hoisted to constants (`Element#to_xml` and
34
+ `Document#to_xml` allocated a Method object per call), and
35
+ `inner_html` serializes element children through a dedicated
36
+ default-options fast path (no kwargs, no options rebuild).
37
+ Measured on a 1.0 MB / 42k-element document: document serialize
38
+ 1.1x → **3.2x faster** than Nokogiri 1.19.4; `inner_html` 0.8x →
39
+ **4.4x faster** (4.2 ms → 1.1 ms per 200 elements). Parse (11x),
40
+ attr reads (9x), text aggregation (11x), and element_children
41
+ walks (parity) all hold.
42
+
43
+ ### Fixed
44
+
45
+ - **#120 closed with a guard**: text ampersands escape on
46
+ serialize — byte-parity specs (parse-and-serialize round-trip
47
+ plus a `content=` write path) now run on every CI leg including
48
+ both Linuxes, so a platform-build serializer divergence fails CI
49
+ instead of shipping. The 1.9.50 Linux build was superseded per
50
+ the verification on the issue.
51
+ - **#124 filed upstream** (leptris/leptris#745): the C-side
52
+ non-standard-entity pre-scan entry — the moxml +61% parse → ~+2%
53
+ case. The binding attaches and wraps it the day it lands.
54
+
55
+ ## [1.9.54] - 2026-09-02
56
+
57
+ ### Changed
58
+
59
+ - **Lockstep with libleptris 1.9.49** (1.9.47 → 1.9.49; no new C
60
+ surface — the audit holds at 255/255 attached == exported):
61
+ - 1.9.47: `leptris_element_copy` keeps namespaces (#721) —
62
+ prefixes, declarations, and resolution on the copied subtree;
63
+ sequence-use `xsl:key` (#720, use-after-free + each item
64
+ indexes).
65
+ - 1.9.48: `xsl:on-completion` sees the final iterate params
66
+ (#729); merge-level `xsl:merge-key` fallback (#731); #730/#732
67
+ triaged against Saxon (quoted `xpath="'$p'"` is the dynamic
68
+ with-param form; multi-top-level results serialize fully —
69
+ libxslt parity).
70
+ - 1.9.49: `xs:` atomic constructors (`xs:integer`/`xs:double`/
71
+ `xs:decimal`/`xs:boolean`/`xs:string`/`xs:anyURI`) resolve in
72
+ any expression — standalone `#xpath` included, specced against
73
+ the Saxon ground truth (`xs:integer('42') + 1` = 43).
74
+ - All three pending specs un-pending (#729/#730/#731); #730
75
+ rewritten as the Saxon-correct pair (quoted form evaluates,
76
+ unquoted raises); sequence-use keys and the constructors
77
+ specced.
78
+
79
+ ### Changed
80
+
81
+ - **`dup` reverted to the C copy** (the payoff of #721's fix):
82
+ `Node#dup`/`Element#dup` and the element indent-unit path drop
83
+ the serialize → re-parse round-trip adopted in 1.9.50 — the C
84
+ copy now preserves every child kind, prefixed names, and
85
+ namespace declarations byte-for-byte, and is attached as the
86
+ fresh document's root so the tree is fully navigable (~436 µs
87
+ vs ~1.2 ms per dup of a 300-element subtree on this bench).
88
+ A new C-level spec pins the namespace fidelity.
89
+
8
90
  ## [1.9.53] - 2026-09-02
9
91
 
10
92
  ### Changed
data/README.adoc CHANGED
@@ -239,6 +239,9 @@ doc.xpath("count(1 to 4)") # => 4.0
239
239
  doc.xpath("//book => count()") # 3.1 arrow
240
240
  doc.xpath("//title ! string(.)") # 3.1 simple map
241
241
  doc.xpath("'v' || count(//book)") # 3.1 concat
242
+ doc.xpath("'42' castable as xs:integer") # 2.0 type ops
243
+ doc.xpath("1.9 cast as xs:integer") # => 1.0 (truncates)
244
+ doc.xpath("//title instance of node()+") # 2.0 sequence types
242
245
  ----
243
246
 
244
247
  Two caveats: `for … return` sequences come back as opaque result
@@ -247,8 +250,8 @@ nodes — consume them through an aggregate (`count(...)`,
247
250
  items; and XQuery-only syntax (`order by`, `try/catch`, map
248
251
  constructors) raises `XPathError` rather than evaluating silently
249
252
  wrong. The remaining grammar gaps — value comparators (`eq`),
250
- quantifiers, sequence types (`instance of`), `intersect`/`except`,
251
- string templates — and XQuery itself are
253
+ quantifiers, `intersect`/`except`, string templates — and XQuery
254
+ itself are
252
255
  https://github.com/leptris/leptris/issues/683[tracked upstream].
253
256
 
254
257
  === Supported CSS selectors
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.46"
11
+ LIBLEPTRIS_VERSION = "1.9.50"
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.53"
4
+ VERSION = "1.9.55"
5
5
  end
@@ -285,7 +285,7 @@ class Leptris::XML::Document
285
285
  @c_ptr, indent: indent, no_decl: no_decl, encoding: encoding)
286
286
  end
287
287
  Leptris::XML::Serialization.to_xml(
288
- Leptris::XML::FFI.method(:leptris_document_serialize_into), @c_ptr,
288
+ Leptris::XML::Serialization::DOCUMENT_SERIALIZE_INTO, @c_ptr,
289
289
  indent: indent, no_decl: no_decl, encoding: encoding)
290
290
  end
291
291
  alias_method :to_s, :to_xml
@@ -251,15 +251,16 @@ class Leptris::XML::Element < Leptris::XML::Node
251
251
  self
252
252
  end
253
253
 
254
- # Deep copy in a NEW document, via serialization round-trip:
255
- # leptris_element_copy drops namespace prefixes and declarations
256
- # outright (leptris/leptris#721; its comment/PI child drop was
257
- # #696, fixed upstream in 1.9.39) the serializer preserves
258
- # every child kind, prefixed names, and namespace declarations,
259
- # and re-parsing rebuilds them all.
254
+ # Deep copy in a NEW document via the C copy (both engine gaps
255
+ # closed: comment/PI children in 1.9.39, namespaces in 1.9.47 —
256
+ # leptris/leptris#696, #721), attached as the fresh document's
257
+ # root so the tree is fully navigable.
260
258
  def dup
261
259
  ensure_alive!
262
- Leptris::XML::Document.parse(to_xml).root
260
+ new_doc = Leptris::XML::Document.create
261
+ copy = Leptris::XML::FFI.leptris_element_copy(@c_ptr, new_doc.c_ptr)
262
+ raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
263
+ new_doc.root = Leptris::XML::Node.wrap(copy, new_doc)
263
264
  end
264
265
  alias_method :clone, :dup
265
266
 
@@ -348,7 +349,8 @@ class Leptris::XML::Element < Leptris::XML::Node
348
349
  def inner_html
349
350
  children.map do |child|
350
351
  case child
351
- when Leptris::XML::Element then child.to_xml
352
+ when Leptris::XML::Element
353
+ Leptris::XML::Serialization.element_xml_default(child.c_ptr)
352
354
  when Leptris::XML::CDATA # before Text: CDATA subclasses it
353
355
  "<![CDATA[#{child.content}]]>"
354
356
  when Leptris::XML::Text
@@ -376,7 +378,7 @@ class Leptris::XML::Element < Leptris::XML::Node
376
378
  return Leptris::XML::Serialization.to_xml_element_unit(
377
379
  self, indent_text, indent: indent) if indent_text.is_a?(String)
378
380
  Leptris::XML::Serialization.to_xml(
379
- Leptris::XML::FFI.method(:leptris_element_serialize_into), @c_ptr,
381
+ Leptris::XML::Serialization::ELEMENT_SERIALIZE_INTO, @c_ptr,
380
382
  indent: indent, no_decl: no_decl, encoding: encoding)
381
383
  end
382
384
 
@@ -894,12 +894,29 @@ module Leptris
894
894
  # leptris_element_serialize_into. The buffer holds the
895
895
  # serialization + NUL and XML output is NUL-free, so the
896
896
  # bounded read is exact.
897
+ # snprintf contract (leptris.h #535): the return is ALWAYS the
898
+ # bytes needed incl. the NUL; the copy happens only when the
899
+ # buffer fits. So the size probe is paid only when the child
900
+ # outgrows the grow-only scratch — one dispatch per call on
901
+ # the fast path instead of two (inner_html serializes each
902
+ # child; the probe was most of the per-child FFI cost).
903
+ SERIALIZE_INITIAL = 256
904
+ private_constant :SERIALIZE_INITIAL
905
+
897
906
  def self.serialize_into_string(ffi_function, c_ptr, options)
898
- need = ffi_function.call(c_ptr, nil, 0, nil, options)
899
- return "" if need.zero?
900
- buffer = scratch_bytes(need)
901
- ffi_function.call(c_ptr, buffer, need, nil, options)
902
- buffer.read_string.force_encoding(Encoding::UTF_8)
907
+ scratch = (Thread.current[:leptris_scratch] ||= {})
908
+ buffer = scratch[:bytes]
909
+ if buffer.nil?
910
+ buffer = scratch[:bytes] =
911
+ ::FFI::MemoryPointer.new(:char, SERIALIZE_INITIAL)
912
+ end
913
+ needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
914
+ if needed > buffer.size
915
+ buffer = scratch_bytes(needed)
916
+ needed = ffi_function.call(c_ptr, buffer, buffer.size, nil, options)
917
+ end
918
+ return "" if needed <= 1
919
+ buffer.read_string(needed - 1).force_encoding(Encoding::UTF_8)
903
920
  end
904
921
 
905
922
  # Byte scratch for serialize-into cycles: the size+fill pair
@@ -369,17 +369,18 @@ class Leptris::XML::Node
369
369
  result
370
370
  end
371
371
 
372
- # Deep copy in a NEW document. Rounds through serialization
373
- # rather than leptris_element_copy: the C copy drops namespace
374
- # prefixes and declarations outright (leptris/leptris#721; the
375
- # comment/PI child drop was #696, fixed upstream in 1.9.39) —
376
- # the serializer preserves every child kind, prefixed names, and
377
- # namespace declarations, and re-parsing rebuilds them all.
372
+ # Deep copy in a NEW document via the C copy (both engine gaps
373
+ # closed: comment/PI children in 1.9.39, namespaces in 1.9.47 —
374
+ # leptris/leptris#696, #721). The copy is attached as the fresh
375
+ # document's root so the tree is fully navigable.
378
376
  def dup
379
377
  ensure_alive!
380
378
  elem_ptr = Leptris::XML::FFI.leptris_node_as_element(@c_ptr)
381
379
  raise Leptris::XML::Error, "dup is only supported for element nodes" if elem_ptr.null?
382
- Leptris::XML::Document.parse(to_xml).root
380
+ new_doc = Leptris::XML::Document.create
381
+ copy = Leptris::XML::FFI.leptris_element_copy(elem_ptr, new_doc.c_ptr)
382
+ raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
383
+ new_doc.root = Leptris::XML::Node.wrap(copy, new_doc)
383
384
  end
384
385
  alias_method :clone, :dup
385
386
 
@@ -18,6 +18,16 @@ module Leptris::XML::Serialization
18
18
  end
19
19
  private_constant :DEFAULT_OPTIONS
20
20
 
21
+ # Bound serializer entries, hoisted: Element#to_xml and the
22
+ # inner_html per-child loop passed FFI.method(:...) — one Method
23
+ # allocation per serialized child.
24
+ ELEMENT_SERIALIZE_INTO =
25
+ Leptris::XML::FFI.method(:leptris_element_serialize_into)
26
+ DOCUMENT_SERIALIZE_INTO =
27
+ Leptris::XML::FFI.method(:leptris_document_serialize_into)
28
+ # Public within the library: Element#to_xml and Document#to_xml
29
+ # pass the hoisted bound methods straight through.
30
+
21
31
  # +ffi_function+ is a bound _serialize_into function. The buffer
22
32
  # cycle lives at the FFI seam (FFI.serialize_into_string); this
23
33
  # module owns only options selection and construction.
@@ -33,6 +43,14 @@ module Leptris::XML::Serialization
33
43
  Leptris::XML::FFI.serialize_into_string(ffi_function, c_ptr, opts.pointer)
34
44
  end
35
45
 
46
+ # Default-options element serialization for inner_html's per-child
47
+ # loop: no kwargs, no options rebuild, no method allocation —
48
+ # one FFI fast-path dispatch per child.
49
+ def self.element_xml_default(c_ptr)
50
+ Leptris::XML::FFI.serialize_into_string(
51
+ ELEMENT_SERIALIZE_INTO, c_ptr, DEFAULT_OPTIONS.pointer)
52
+ end
53
+
36
54
  # Display-form document serialization (libleptris 1.9.9, #129):
37
55
  # the ext struct's indent_text hands ALL whitespace to the
38
56
  # formatter — text and mixed content indent too. Display-oriented:
@@ -97,14 +115,17 @@ module Leptris::XML::Serialization
97
115
 
98
116
  # Element-face indent unit (leptris-ruby#109 residual 2): no
99
117
  # element-level ext-serialize entry exists yet, so the unit path
100
- # rounds the subtree through serialization into a fresh document
101
- # (leptris_element_copy drops namespacesleptris/leptris#721)
102
- # and serializes that document without a declaration — identical
103
- # output to the element serializer for the standard layout, with
104
- # the unit and every child kind intact.
118
+ # C-copies the subtree into a fresh document (the copy keeps
119
+ # every child kind and namespace since leptris 1.9.47 — #696,
120
+ # #721) and serializes that document without a declaration —
121
+ # identical output to the element serializer for the standard
122
+ # layout, with the unit and every child kind intact.
105
123
  def self.to_xml_element_unit(element, unit, indent: 0)
106
- document = Leptris::XML::Document.parse(element.to_xml)
124
+ document = Leptris::XML::Document.create
107
125
  begin
126
+ copy = Leptris::XML::FFI.leptris_element_copy(element.c_ptr, document.c_ptr)
127
+ raise Leptris::XML::Error, "leptris_element_copy failed" if copy.null?
128
+ document.root = Leptris::XML::Node.wrap(copy, document)
108
129
  to_xml_indent_unit(document.c_ptr, unit,
109
130
  indent: indent, no_decl: true)
110
131
  ensure
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.53
4
+ version: 1.9.55
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Ribose Inc.