leptris 1.2.0 → 1.3.0
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 +4 -4
- data/CHANGELOG.md +45 -0
- data/Rakefile +5 -3
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +19 -1
- data/lib/leptris/xml/ffi.rb +45 -2
- data/lib/leptris/xml/searchable.rb +24 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e181133c2d381d3ed81fbab6ad41b99c0a72ed89cc651a7882c069eee06535fa
|
|
4
|
+
data.tar.gz: 768c1463a6746f331251d10f5499293b4c00c0914037fee33a08725b7855ac7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0c45bea9042ffec94231c4c799aa43145592d1dff5268ee75e75212b015be689ad875dac84b76ee8a7cf0624bb1b983c39b3dba8ef3966bc8591275648c7738
|
|
7
|
+
data.tar.gz: eb2803ee6a55c4dcd543678bcb9899c5651269d9737d92bffa9f8f156ddb87b22838f6c12b610644ee585ed6854041f4815f5b4a6f1de4cceb8dd8ae5aceca50
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-08-23
|
|
9
|
+
|
|
10
|
+
Lockstep with libleptris 1.3.0 — takes full advantage of the
|
|
11
|
+
concurrency-release surface.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **EXSLT extension pack**: `Document#exslt` enables the first-party
|
|
16
|
+
str:/set:/math: function pack (replace, tokenize, split, concat,
|
|
17
|
+
padding; distinct, intersection, difference, leading, trailing;
|
|
18
|
+
max, min, abs, sqrt, power) as native C handlers on that document.
|
|
19
|
+
- `Document#last_error` — per-document error retrieval (thread-safe
|
|
20
|
+
successor to the library-global string).
|
|
21
|
+
- `leptris_thread_cleanup` bound — optional release of per-thread
|
|
22
|
+
registry entries when worker threads exit.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Namespace-bound XPath builds its binding set in **one FFI call**
|
|
27
|
+
via `leptris_xpath_ns_set_new_from_pairs` (flat alternating
|
|
28
|
+
prefix/URI array through the CStringArray adapter) instead of
|
|
29
|
+
N+1 calls.
|
|
30
|
+
- `NodeSet#each` fetches mixed-kind results through
|
|
31
|
+
`leptris_xpath_result_get_nodes_ex`, which copies ALL node kinds —
|
|
32
|
+
the per-index fallback for the under-copying legacy accessor
|
|
33
|
+
(leptris#477 residual) is gone.
|
|
34
|
+
- `leptris_version_components` is now officially declared upstream
|
|
35
|
+
(it was exported-but-undeclared; the export gate caught it).
|
|
36
|
+
|
|
37
|
+
## [1.2.1] - 2026-08-23
|
|
38
|
+
|
|
39
|
+
Completes the libleptris 1.2.0 public surface in the binding.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Namespace-bound XPath**: `doc.xpath("//p:title", "p" => "urn:p")`
|
|
44
|
+
(Nokogiri parity) — expression prefixes now resolve to caller-supplied
|
|
45
|
+
URIs regardless of the prefixes the document declared, via
|
|
46
|
+
`leptris_xpath_ns_set_new/_add/_free` + `leptris_xpath_eval_ns`. The
|
|
47
|
+
binding set is built per call and freed under `ensure`. Previously
|
|
48
|
+
the trailing namespace hash was silently ignored.
|
|
49
|
+
- `leptris_error_message` / `leptris_last_error` bound; the status seam
|
|
50
|
+
and parse errors now append the library's last-error detail when
|
|
51
|
+
present (best-effort: it is a library-global string).
|
|
52
|
+
|
|
8
53
|
## [1.2.0] - 2026-08-23
|
|
9
54
|
|
|
10
55
|
Lockstep with libleptris 1.2.0.
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.3.0"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
|
@@ -34,8 +34,10 @@ task :compile do
|
|
|
34
34
|
sh "cmake --build #{build}/build --config Release -j 4"
|
|
35
35
|
# Windows names the shared library leptris.dll (no "lib" prefix);
|
|
36
36
|
# vendoring under the uniform libleptris.* name keeps the FFI
|
|
37
|
-
# search order simple.
|
|
38
|
-
|
|
37
|
+
# search order simple. libleptris >= 1.3.0 renames the DLL to
|
|
38
|
+
# libleptris.dll on Windows too (leptris/leptris#507, issue
|
|
39
|
+
# TODO.concurrency/05) — accept both names.
|
|
40
|
+
lib = Dir.glob("#{build}/build/**/libleptris.{dylib,so,dll}").first ||
|
|
39
41
|
Dir.glob("#{build}/build/**/leptris.dll").first
|
|
40
42
|
raise "libleptris shared library not found after build" unless lib
|
|
41
43
|
ext = File.extname(lib)
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -50,7 +50,8 @@ class Leptris::XML::Document
|
|
|
50
50
|
if raw.null?
|
|
51
51
|
status = status_ptr.read_int
|
|
52
52
|
raise Leptris::XML::ParseError,
|
|
53
|
-
"leptris_parse_string failed (status=#{status})"
|
|
53
|
+
"leptris_parse_string failed (status=#{status}): " +
|
|
54
|
+
Leptris::XML::FFI.status_message(status)
|
|
54
55
|
end
|
|
55
56
|
wrap(raw)
|
|
56
57
|
end
|
|
@@ -222,6 +223,23 @@ class Leptris::XML::Document
|
|
|
222
223
|
@wrapper_cache.clear
|
|
223
224
|
end
|
|
224
225
|
|
|
226
|
+
# Enable the first-party EXSLT-style extension pack on this
|
|
227
|
+
# document: str:/set:/math: prefixed functions (replace, tokenize,
|
|
228
|
+
# split, concat, padding; distinct, intersection, difference,
|
|
229
|
+
# leading, trailing; max, min, abs, sqrt, power) as native C
|
|
230
|
+
# handlers. Returns self for chaining.
|
|
231
|
+
def exslt
|
|
232
|
+
Leptris::XML::FFI.check_status(
|
|
233
|
+
Leptris::XML::FFI.leptris_exslt_enable(@c_ptr))
|
|
234
|
+
self
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The most recent error recorded against this document, or nil.
|
|
238
|
+
def last_error
|
|
239
|
+
msg = Leptris::XML::FFI.leptris_document_last_error(@c_ptr)
|
|
240
|
+
msg.nil? || msg.empty? ? nil : msg
|
|
241
|
+
end
|
|
242
|
+
|
|
225
243
|
def name; "document"; end
|
|
226
244
|
def document; self; end
|
|
227
245
|
def encoding
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -23,6 +23,7 @@ module Leptris
|
|
|
23
23
|
typedef :pointer, :leptris_attribute
|
|
24
24
|
typedef :pointer, :leptris_doctype
|
|
25
25
|
typedef :pointer, :leptris_xpath_result
|
|
26
|
+
typedef :pointer, :leptris_xpath_ns_set
|
|
26
27
|
typedef :pointer, :leptris_xpath_var_set
|
|
27
28
|
typedef :pointer, :leptris_sax_parser
|
|
28
29
|
typedef :int, :leptris_status
|
|
@@ -343,6 +344,10 @@ module Leptris
|
|
|
343
344
|
[:leptris_document, :string, :pointer, :pointer], :leptris_status
|
|
344
345
|
attach_function :leptris_xpath_result_get_nodes,
|
|
345
346
|
[:leptris_xpath_result, :pointer, :size_t], :size_t
|
|
347
|
+
# v1.3.0: copies ALL node kinds (the legacy accessor drops
|
|
348
|
+
# non-element entries) and can also report per-entry kinds.
|
|
349
|
+
attach_function :leptris_xpath_result_get_nodes_ex,
|
|
350
|
+
[:leptris_xpath_result, :pointer, :pointer, :size_t], :size_t
|
|
346
351
|
attach_function :leptris_xpath_result_boolean,
|
|
347
352
|
[:leptris_xpath_result], :int
|
|
348
353
|
attach_function :leptris_xpath_result_number,
|
|
@@ -365,6 +370,22 @@ module Leptris
|
|
|
365
370
|
attach_function :leptris_xpath_variable_set_string,
|
|
366
371
|
[:leptris_xpath_var_set, :string, :string], :leptris_status
|
|
367
372
|
|
|
373
|
+
# Namespace-bound XPath (v1.2.0): expression prefixes resolve to
|
|
374
|
+
# caller-supplied URIs regardless of the document's declarations.
|
|
375
|
+
attach_function :leptris_xpath_ns_set_new,
|
|
376
|
+
[], :leptris_xpath_ns_set
|
|
377
|
+
attach_function :leptris_xpath_ns_set_free,
|
|
378
|
+
[:leptris_xpath_ns_set], :void
|
|
379
|
+
attach_function :leptris_xpath_ns_set_add,
|
|
380
|
+
[:leptris_xpath_ns_set, :string, :string], :leptris_status
|
|
381
|
+
# One-call constructor: flat array of 2*pair_count alternating
|
|
382
|
+
# prefix/URI strings (v1.3.0).
|
|
383
|
+
attach_function :leptris_xpath_ns_set_new_from_pairs,
|
|
384
|
+
[:pointer, :size_t], :leptris_xpath_ns_set
|
|
385
|
+
attach_function :leptris_xpath_eval_ns,
|
|
386
|
+
[:leptris_document, :leptris_element, :string, :leptris_xpath_ns_set],
|
|
387
|
+
:leptris_xpath_result
|
|
388
|
+
|
|
368
389
|
attach_function :leptris_sax_parse,
|
|
369
390
|
[:string, :size_t, :pointer, :pointer], :int
|
|
370
391
|
attach_function :leptris_sax_parser_create,
|
|
@@ -419,6 +440,19 @@ module Leptris
|
|
|
419
440
|
attach_function :leptris_document_set_allocators,
|
|
420
441
|
[:leptris_document, :pointer, :pointer], :leptris_status
|
|
421
442
|
attach_function :leptris_status_string, [:leptris_status], :string
|
|
443
|
+
attach_function :leptris_error_message, [:leptris_status], :string
|
|
444
|
+
# Thread-local since v1.3.0; reliable under the
|
|
445
|
+
# one-document-per-thread contract.
|
|
446
|
+
attach_function :leptris_last_error, [], :string
|
|
447
|
+
attach_function :leptris_document_last_error,
|
|
448
|
+
[:leptris_document], :string
|
|
449
|
+
# Optional: release per-thread registry entries when a worker
|
|
450
|
+
# thread exits (long-lived threads never need it).
|
|
451
|
+
attach_function :leptris_thread_cleanup, [], :void
|
|
452
|
+
# First-party EXSLT-style extension pack (str:/set:/math:),
|
|
453
|
+
# registered natively on one document.
|
|
454
|
+
attach_function :leptris_exslt_enable,
|
|
455
|
+
[:leptris_document], :leptris_status
|
|
422
456
|
|
|
423
457
|
LEPTRIS_OK = 0
|
|
424
458
|
LEPTRIS_ERROR_MEMORY = -1
|
|
@@ -467,13 +501,22 @@ module Leptris
|
|
|
467
501
|
ptr.read_string.tap { leptris_free_string(ptr) }
|
|
468
502
|
end
|
|
469
503
|
|
|
470
|
-
# Single status seam: turns a C status code into a Ruby error
|
|
504
|
+
# Single status seam: turns a C status code into a Ruby error,
|
|
505
|
+
# appending the library's last-error detail when present. The
|
|
506
|
+
# detail is a library-global string, so it can only be treated
|
|
507
|
+
# as best-effort context.
|
|
471
508
|
def self.check_status(status)
|
|
472
509
|
unless status == LEPTRIS_OK
|
|
473
|
-
raise Leptris::XML::Error,
|
|
510
|
+
raise Leptris::XML::Error, status_message(status)
|
|
474
511
|
end
|
|
475
512
|
status
|
|
476
513
|
end
|
|
514
|
+
|
|
515
|
+
def self.status_message(status)
|
|
516
|
+
base = leptris_status_string(status).to_s
|
|
517
|
+
detail = leptris_last_error.to_s
|
|
518
|
+
detail.empty? ? base : "#{base} (#{detail})"
|
|
519
|
+
end
|
|
477
520
|
end
|
|
478
521
|
end
|
|
479
522
|
end
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Leptris::XML::Searchable
|
|
4
4
|
def xpath(*paths)
|
|
5
|
-
handler,
|
|
5
|
+
handler, ns, _vars = parse_search_args(paths)
|
|
6
6
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
7
7
|
expr = paths.join(" | ")
|
|
8
8
|
|
|
9
9
|
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
10
10
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
11
11
|
|
|
12
|
-
result_ptr =
|
|
12
|
+
result_ptr =
|
|
13
|
+
if ns && !ns.empty?
|
|
14
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
15
|
+
else
|
|
16
|
+
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
17
|
+
end
|
|
13
18
|
if result_ptr.null?
|
|
14
19
|
raise Leptris::XML::XPathError,
|
|
15
20
|
Leptris::XML::FFI.leptris_status_string(Leptris::XML::FFI::LEPTRIS_ERROR_XPATH)
|
|
@@ -50,6 +55,23 @@ module Leptris::XML::Searchable
|
|
|
50
55
|
|
|
51
56
|
protected
|
|
52
57
|
|
|
58
|
+
# Builds a caller-owned namespace binding set in one FFI call
|
|
59
|
+
# (ns_set_new_from_pairs takes a flat alternating prefix/URI array,
|
|
60
|
+
# the same wire format CStringArray owns), evaluates via
|
|
61
|
+
# leptris_xpath_eval_ns, and frees the set regardless of outcome.
|
|
62
|
+
def xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
63
|
+
flat = ns.flat_map { |prefix, uri| [prefix.to_s, uri.to_s] }
|
|
64
|
+
buffer, _anchors = Leptris::XML::CStringArray.to_c(flat)
|
|
65
|
+
set = Leptris::XML::FFI.leptris_xpath_ns_set_new_from_pairs(
|
|
66
|
+
buffer, flat.length / 2)
|
|
67
|
+
raise Leptris::XML::Error, "leptris_xpath_ns_set_new_from_pairs failed" if set.null?
|
|
68
|
+
begin
|
|
69
|
+
Leptris::XML::FFI.leptris_xpath_eval_ns(doc_ptr, context_ptr, expr, set)
|
|
70
|
+
ensure
|
|
71
|
+
Leptris::XML::FFI.leptris_xpath_ns_set_free(set)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
53
75
|
def parse_search_args(args)
|
|
54
76
|
handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
|
|
55
77
|
args = args - [handler] if handler
|