leptris 1.1.2 → 1.2.1
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 +50 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/attr.rb +11 -0
- data/lib/leptris/xml/document.rb +23 -1
- data/lib/leptris/xml/element.rb +6 -1
- data/lib/leptris/xml/ffi.rb +30 -2
- data/lib/leptris/xml/node.rb +11 -2
- data/lib/leptris/xml/searchable.rb +23 -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: e9d9e951ee8e7b15451e02933c6e5e80d6cb78fa970ff4411153ec1358c50ab1
|
|
4
|
+
data.tar.gz: 5aab53987963478c27bb0d9bfe96e804672dcc6b7da1842cc3422f9e0b91c235
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7de27428d6e469b405889481a78bdbe1680d28e4b8c4bd2041ccfb49dd3b9a620359f5d66a4bc4f4573e16c332916348cf4313bedbb1c5d1876cf08cb64b243e
|
|
7
|
+
data.tar.gz: c71f63da347773adfd341782dd10326ff5993c673080e63554013eed453137014f76f2b4a0474b9fa452c5009f573d4d65f01213822016e296735a0a1eea30e3
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,56 @@ 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.2.1] - 2026-08-23
|
|
9
|
+
|
|
10
|
+
Completes the libleptris 1.2.0 public surface in the binding.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Namespace-bound XPath**: `doc.xpath("//p:title", "p" => "urn:p")`
|
|
15
|
+
(Nokogiri parity) — expression prefixes now resolve to caller-supplied
|
|
16
|
+
URIs regardless of the prefixes the document declared, via
|
|
17
|
+
`leptris_xpath_ns_set_new/_add/_free` + `leptris_xpath_eval_ns`. The
|
|
18
|
+
binding set is built per call and freed under `ensure`. Previously
|
|
19
|
+
the trailing namespace hash was silently ignored.
|
|
20
|
+
- `leptris_error_message` / `leptris_last_error` bound; the status seam
|
|
21
|
+
and parse errors now append the library's last-error detail when
|
|
22
|
+
present (best-effort: it is a library-global string).
|
|
23
|
+
|
|
24
|
+
## [1.2.0] - 2026-08-23
|
|
25
|
+
|
|
26
|
+
Lockstep with libleptris 1.2.0.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `Leptris::XML::Document.create` — empty document with its own memory
|
|
31
|
+
pool (backs `leptris_document_create`); elements are created against
|
|
32
|
+
it via the factories and attached with `root=`.
|
|
33
|
+
- `Document#root=` — attach an element as the document root
|
|
34
|
+
(`leptris_document_set_root`). Companion to the moxml Leptris
|
|
35
|
+
adapter (lutaml/moxml#96).
|
|
36
|
+
- `Attr#to_xml` — serialized `name="value"` form with the five XML
|
|
37
|
+
special characters escaped.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `Node#text` / `#inner_text` now dispatch to `#content` instead of
|
|
42
|
+
aliasing the base implementation — the alias snapshot meant subclass
|
|
43
|
+
overrides (`Text#content`, `Comment#content`, …) were never seen
|
|
44
|
+
through `text`.
|
|
45
|
+
- `Element#namespace` carries the element's own prefix, so consumers
|
|
46
|
+
can distinguish `{"p" => "urn:p"}` from the default namespace.
|
|
47
|
+
- CI/Rakefile pin libleptris v1.2.0, whose fixes resolve leptris#477
|
|
48
|
+
(mixed-nodeset kinds, `node_name`/`node_value` on non-element
|
|
49
|
+
entries, and `//node()` now including the context root). NodeSet
|
|
50
|
+
keeps its per-index fallback for the still-under-copying batch
|
|
51
|
+
accessor.
|
|
52
|
+
|
|
53
|
+
### Unbound v1.2.0 surface (follow-up)
|
|
54
|
+
|
|
55
|
+
Namespace-bound XPath (`leptris_xpath_ns_set_*`, `leptris_xpath_eval_ns`)
|
|
56
|
+
and error introspection (`leptris_error_message`, `leptris_last_error`).
|
|
57
|
+
|
|
8
58
|
## [1.1.1] - 2026-08-22
|
|
9
59
|
|
|
10
60
|
### Added
|
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.2.0"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/attr.rb
CHANGED
|
@@ -29,6 +29,17 @@ class Leptris::XML::Attr
|
|
|
29
29
|
def to_s; @value; end
|
|
30
30
|
def to_str; @value; end
|
|
31
31
|
|
|
32
|
+
# Serialized attribute form: name="value" with the five XML special
|
|
33
|
+
# characters escaped in the value.
|
|
34
|
+
def to_xml
|
|
35
|
+
escaped = @value.to_s.gsub("&", "&")
|
|
36
|
+
.gsub("<", "<")
|
|
37
|
+
.gsub(">", ">")
|
|
38
|
+
.gsub('"', """)
|
|
39
|
+
.gsub("'", "'")
|
|
40
|
+
"#{@name}=\"#{escaped}\""
|
|
41
|
+
end
|
|
42
|
+
|
|
32
43
|
def ==(other)
|
|
33
44
|
other.is_a?(Leptris::XML::Attr) &&
|
|
34
45
|
@name == other.name && @value == other.value &&
|
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
|
|
@@ -66,6 +67,16 @@ class Leptris::XML::Document
|
|
|
66
67
|
wrap(raw)
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# Create an empty document (no root element) backed by its own memory
|
|
71
|
+
# pool. Elements for the tree are created against it via
|
|
72
|
+
# #create_element and friends, then attached with #root=.
|
|
73
|
+
def self.create
|
|
74
|
+
raw = Leptris::XML::FFI.leptris_document_create
|
|
75
|
+
raise Leptris::XML::Error,
|
|
76
|
+
"leptris_document_create failed" if raw.null?
|
|
77
|
+
wrap(raw)
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
# Convert a raw LeptrisDocument pointer into a Ruby Document with safe
|
|
70
81
|
# GC lifetime management. The finalizer captures the raw address
|
|
71
82
|
# integer (not the Document or Pointer object — those would prevent
|
|
@@ -107,6 +118,17 @@ class Leptris::XML::Document
|
|
|
107
118
|
Leptris::XML::Node.wrap(ptr, self)
|
|
108
119
|
end
|
|
109
120
|
|
|
121
|
+
# Attach +element+ as the document's root element. The element must
|
|
122
|
+
# have been created against this document and must not already have
|
|
123
|
+
# a parent. Any previous root is left detached (still owned by the
|
|
124
|
+
# document's pool until #free).
|
|
125
|
+
def root=(element)
|
|
126
|
+
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
127
|
+
Leptris::XML::FFI.check_status(
|
|
128
|
+
Leptris::XML::FFI.leptris_document_set_root(@c_ptr, element.c_ptr))
|
|
129
|
+
element
|
|
130
|
+
end
|
|
131
|
+
|
|
110
132
|
def create_element(name)
|
|
111
133
|
ptr = Leptris::XML::FFI.leptris_element_create(@c_ptr, name)
|
|
112
134
|
raise Leptris::XML::Error, "leptris_element_create failed" if ptr.null?
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -191,7 +191,12 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
191
191
|
def namespace
|
|
192
192
|
uri = Leptris::XML::FFI.leptris_element_namespace(@c_ptr)
|
|
193
193
|
return nil if uri.nil? || uri.empty?
|
|
194
|
-
|
|
194
|
+
# The resolved namespace is reached via this element's own prefix,
|
|
195
|
+
# so carry it through: consumers that distinguish
|
|
196
|
+
# {"p" => "urn:p"} from the default {"nil => "urn:p"} need it.
|
|
197
|
+
prefix = Leptris::XML::FFI.leptris_element_prefix(@c_ptr)
|
|
198
|
+
prefix = nil if prefix.nil? || prefix.empty?
|
|
199
|
+
Leptris::XML::Namespace.new(self, uri, prefix: prefix)
|
|
195
200
|
end
|
|
196
201
|
|
|
197
202
|
def namespace_definitions
|
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
|
|
@@ -66,6 +67,10 @@ module Leptris
|
|
|
66
67
|
[:string, :pointer], :pointer
|
|
67
68
|
attach_function :leptris_document_free,
|
|
68
69
|
[:leptris_document], :void
|
|
70
|
+
attach_function :leptris_document_create,
|
|
71
|
+
[], :leptris_document
|
|
72
|
+
attach_function :leptris_document_set_root,
|
|
73
|
+
[:leptris_document, :leptris_element], :leptris_status
|
|
69
74
|
attach_function :leptris_document_root,
|
|
70
75
|
[:leptris_document], :leptris_element
|
|
71
76
|
attach_function :leptris_document_encoding,
|
|
@@ -361,6 +366,18 @@ module Leptris
|
|
|
361
366
|
attach_function :leptris_xpath_variable_set_string,
|
|
362
367
|
[:leptris_xpath_var_set, :string, :string], :leptris_status
|
|
363
368
|
|
|
369
|
+
# Namespace-bound XPath (v1.2.0): expression prefixes resolve to
|
|
370
|
+
# caller-supplied URIs regardless of the document's declarations.
|
|
371
|
+
attach_function :leptris_xpath_ns_set_new,
|
|
372
|
+
[], :leptris_xpath_ns_set
|
|
373
|
+
attach_function :leptris_xpath_ns_set_free,
|
|
374
|
+
[:leptris_xpath_ns_set], :void
|
|
375
|
+
attach_function :leptris_xpath_ns_set_add,
|
|
376
|
+
[:leptris_xpath_ns_set, :string, :string], :leptris_status
|
|
377
|
+
attach_function :leptris_xpath_eval_ns,
|
|
378
|
+
[:leptris_document, :leptris_element, :string, :leptris_xpath_ns_set],
|
|
379
|
+
:leptris_xpath_result
|
|
380
|
+
|
|
364
381
|
attach_function :leptris_sax_parse,
|
|
365
382
|
[:string, :size_t, :pointer, :pointer], :int
|
|
366
383
|
attach_function :leptris_sax_parser_create,
|
|
@@ -415,6 +432,8 @@ module Leptris
|
|
|
415
432
|
attach_function :leptris_document_set_allocators,
|
|
416
433
|
[:leptris_document, :pointer, :pointer], :leptris_status
|
|
417
434
|
attach_function :leptris_status_string, [:leptris_status], :string
|
|
435
|
+
attach_function :leptris_error_message, [:leptris_status], :string
|
|
436
|
+
attach_function :leptris_last_error, [], :string
|
|
418
437
|
|
|
419
438
|
LEPTRIS_OK = 0
|
|
420
439
|
LEPTRIS_ERROR_MEMORY = -1
|
|
@@ -463,13 +482,22 @@ module Leptris
|
|
|
463
482
|
ptr.read_string.tap { leptris_free_string(ptr) }
|
|
464
483
|
end
|
|
465
484
|
|
|
466
|
-
# Single status seam: turns a C status code into a Ruby error
|
|
485
|
+
# Single status seam: turns a C status code into a Ruby error,
|
|
486
|
+
# appending the library's last-error detail when present. The
|
|
487
|
+
# detail is a library-global string, so it can only be treated
|
|
488
|
+
# as best-effort context.
|
|
467
489
|
def self.check_status(status)
|
|
468
490
|
unless status == LEPTRIS_OK
|
|
469
|
-
raise Leptris::XML::Error,
|
|
491
|
+
raise Leptris::XML::Error, status_message(status)
|
|
470
492
|
end
|
|
471
493
|
status
|
|
472
494
|
end
|
|
495
|
+
|
|
496
|
+
def self.status_message(status)
|
|
497
|
+
base = leptris_status_string(status).to_s
|
|
498
|
+
detail = leptris_last_error.to_s
|
|
499
|
+
detail.empty? ? base : "#{base} (#{detail})"
|
|
500
|
+
end
|
|
473
501
|
end
|
|
474
502
|
end
|
|
475
503
|
end
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -45,8 +45,17 @@ class Leptris::XML::Node
|
|
|
45
45
|
def content
|
|
46
46
|
raise NotImplementedError, "#{self.class}#content not implemented"
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
# Dispatching defs, not alias_method: an alias snapshots this base
|
|
50
|
+
# #content (the raise), so subclass overrides would never be seen
|
|
51
|
+
# through the alias. A plain method resolves #content per-call.
|
|
52
|
+
def text
|
|
53
|
+
content
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def inner_text
|
|
57
|
+
content
|
|
58
|
+
end
|
|
50
59
|
|
|
51
60
|
def type
|
|
52
61
|
Leptris::XML::FFI.leptris_node_get_type(@c_ptr)
|
|
@@ -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,22 @@ module Leptris::XML::Searchable
|
|
|
50
55
|
|
|
51
56
|
protected
|
|
52
57
|
|
|
58
|
+
# Builds a caller-owned namespace binding set, evaluates via
|
|
59
|
+
# leptris_xpath_eval_ns, and frees the set regardless of outcome.
|
|
60
|
+
def xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
61
|
+
set = Leptris::XML::FFI.leptris_xpath_ns_set_new
|
|
62
|
+
begin
|
|
63
|
+
ns.each do |prefix, uri|
|
|
64
|
+
Leptris::XML::FFI.check_status(
|
|
65
|
+
Leptris::XML::FFI.leptris_xpath_ns_set_add(
|
|
66
|
+
set, prefix.to_s, uri.to_s))
|
|
67
|
+
end
|
|
68
|
+
Leptris::XML::FFI.leptris_xpath_eval_ns(doc_ptr, context_ptr, expr, set)
|
|
69
|
+
ensure
|
|
70
|
+
Leptris::XML::FFI.leptris_xpath_ns_set_free(set)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
53
74
|
def parse_search_args(args)
|
|
54
75
|
handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
|
|
55
76
|
args = args - [handler] if handler
|