leptris 1.9.156.0 → 1.9.156.2
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 +60 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +13 -0
- data/lib/leptris/xml/ffi.rb +4 -0
- data/lib/leptris/xml/searchable.rb +60 -7
- data/lib/leptris/xml/xpath.rb +20 -4
- 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: c649879fc6a03c38e8c36df987c079271d62583992ae6836f23f53dae102a82e
|
|
4
|
+
data.tar.gz: 91c1a0d1f5101f73841bfee5ca6fd4ad07bbe36a9b6035c7542d3c43bac14b7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12b2aaa87ab88a85f9c2023a976869a30ae53bb6671d8bf47d0097e87870a5b8eb3fe0b728d564ae6312b46f2343b2a3db5720cdd6b4ce27a86c1ddb9826547e
|
|
7
|
+
data.tar.gz: 5435202bd16c7175dfb1a36d49bf3027ed562729f7f99a1a98b393aa9daf85f0a338e019f953864ad07ee13be3457f15b2e7b6e730e67db9d8adf2c4b2f76703
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,66 @@ 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.156.2] - 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **#183: XPath version selection** (mirroring leptris-py#105):
|
|
13
|
+
`xpath`/`at_xpath` take a trailing `version:` selector —
|
|
14
|
+
`doc.xpath("count(//b)", version: "1.0")` evaluates through the
|
|
15
|
+
strict 1.0 engine lane (3.x syntax raises), `"3.1"` the full
|
|
16
|
+
grammar; `XPath.compile(expr, version:)` pins compiled
|
|
17
|
+
expressions the same way. Carried positionally — a declared
|
|
18
|
+
keyword is impossible without breaking the namespace-binding
|
|
19
|
+
hash channel (Ruby converts a trailing String-keyed hash into
|
|
20
|
+
keywords the moment one exists), so a `:version` key whose
|
|
21
|
+
VALUE is a version selector ("1.0"/"3.1"/:xpath10/:xpath31)
|
|
22
|
+
selects the channel; dotted-number values raise at the boundary
|
|
23
|
+
with the valid values; any other value stays a prefix binding
|
|
24
|
+
(a prefix literally named "version" keeps working). `version:`
|
|
25
|
+
combined with namespace bindings raises (the versioned engine
|
|
26
|
+
entry takes no ns set yet), as does `version:` on css. The nil
|
|
27
|
+
default keeps the existing eval path verbatim.
|
|
28
|
+
\n## [1.9.156.1] - 2026-09-13
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`Element#create_child(name)`** — the fused create+append
|
|
33
|
+
(libleptris 1.9.153 `leptris_element_create_child`, audit 290)
|
|
34
|
+
landed on main seconds after the 1.9.156.0 platform gems were
|
|
35
|
+
built and published, so that release ships without the binding
|
|
36
|
+
face. This patch release carries it: one C call, tree semantics
|
|
37
|
+
identical to `create_element` + `add_child`, the builder-shape
|
|
38
|
+
single call.
|
|
39
|
+
|
|
40
|
+
## [1.9.156.0] - 2026-09-13
|
|
41
|
+
|
|
42
|
+
### Security/correctness
|
|
43
|
+
|
|
44
|
+
- **Lockstep with libleptris 1.9.156 (1.9.152 → 1.9.156)** — carries
|
|
45
|
+
the **#1038 heap-corruption fix** (engine regression window:
|
|
46
|
+
libleptris 1.9.151–1.9.155; root-doc map entries outlived their
|
|
47
|
+
document and wrote through freed pools — roaming crashes in
|
|
48
|
+
downstream binding suites at ~5% of runs). The two gem versions
|
|
49
|
+
that embedded the affected engine, **1.9.152.0 and 1.9.152.1,
|
|
50
|
+
are YANKED**; upgrade to 1.9.156.0.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`Element#create_child(name)`** (libleptris 1.9.153): the fused
|
|
55
|
+
create+append — one document resolution, one C call, tree
|
|
56
|
+
semantics identical to `create_element` + `add_child`; the
|
|
57
|
+
builder-shape single call.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Engine: WHATWG template-mode gaps closed (1.9.154/1.9.155):
|
|
62
|
+
frame/frameset drops inside templates, in-column-group token
|
|
63
|
+
gating, `</template>` inside `<select>`, foreign-content
|
|
64
|
+
template end-tag matching — html5lib corpus 1197 → 1206;
|
|
65
|
+
Nokogiri parity floor held. Audit 289 → 290
|
|
66
|
+
(leptris_element_create_child).
|
|
67
|
+
|
|
8
68
|
## [1.9.152.1] - 2026-09-13
|
|
9
69
|
|
|
10
70
|
### Fixed
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -331,6 +331,19 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
331
331
|
end
|
|
332
332
|
alias_method :clone, :dup
|
|
333
333
|
|
|
334
|
+
# Fused create+append (libleptris 1.9.153): builds a child
|
|
335
|
+
# element of +name+ in this element's document and attaches it
|
|
336
|
+
# in ONE C call — the builder-shape single call
|
|
337
|
+
# (leptris_element_create_child). Tree semantics identical to
|
|
338
|
+
# document.create_element(name) + add_child.
|
|
339
|
+
def create_child(name)
|
|
340
|
+
ensure_writable!
|
|
341
|
+
ptr = Leptris::XML::FFI.leptris_element_create_child(@c_ptr, name.to_s)
|
|
342
|
+
raise Leptris::XML::Error,
|
|
343
|
+
"leptris_element_create_child failed for #{name.inspect}" if ptr.null?
|
|
344
|
+
Leptris::XML::Node.wrap_fresh(ptr, @document, Leptris::XML::FFI::NODE_ELEMENT)
|
|
345
|
+
end
|
|
346
|
+
|
|
334
347
|
def add_child(node_or_markup)
|
|
335
348
|
ensure_writable!
|
|
336
349
|
case node_or_markup
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -457,6 +457,10 @@ attach_function :leptris_parse_string,
|
|
|
457
457
|
[:leptris_element, :string], :leptris_status
|
|
458
458
|
attach_function :leptris_element_append_child,
|
|
459
459
|
[:leptris_element, :leptris_element], :leptris_status
|
|
460
|
+
# Fused create+append (libleptris 1.9.153): one document
|
|
461
|
+
# resolution, identical tree semantics to the two-call pair.
|
|
462
|
+
attach_function :leptris_element_create_child,
|
|
463
|
+
[:leptris_element, :string], :leptris_element
|
|
460
464
|
attach_function :leptris_element_prepend_child,
|
|
461
465
|
[:leptris_element, :leptris_element], :leptris_status
|
|
462
466
|
attach_function :leptris_element_insert_before,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Leptris::XML::Searchable
|
|
4
|
+
# Values that make a :version key the version channel (#183,
|
|
5
|
+
# mirroring leptris-py#105); anything else is a namespace prefix.
|
|
6
|
+
VERSION_SELECTORS = ["1.0", "3.1", :xpath10, :xpath31].freeze
|
|
7
|
+
|
|
4
8
|
def xpath(*paths)
|
|
5
|
-
handler, ns,
|
|
9
|
+
handler, ns, version = parse_search_args(paths)
|
|
6
10
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
7
11
|
expr = paths.join(" | ")
|
|
8
12
|
|
|
@@ -10,7 +14,16 @@ module Leptris::XML::Searchable
|
|
|
10
14
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
11
15
|
|
|
12
16
|
result_ptr =
|
|
13
|
-
if
|
|
17
|
+
if version
|
|
18
|
+
if ns && !ns.empty?
|
|
19
|
+
raise ArgumentError,
|
|
20
|
+
"version: cannot be combined with namespace bindings " \
|
|
21
|
+
"(the versioned engine entry takes no ns set yet)"
|
|
22
|
+
end
|
|
23
|
+
Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
24
|
+
doc_ptr, context_ptr, expr,
|
|
25
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
26
|
+
elsif ns && !ns.empty?
|
|
14
27
|
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
15
28
|
else
|
|
16
29
|
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
@@ -24,7 +37,7 @@ module Leptris::XML::Searchable
|
|
|
24
37
|
end
|
|
25
38
|
|
|
26
39
|
def at_xpath(*paths)
|
|
27
|
-
handler, ns,
|
|
40
|
+
handler, ns, version = parse_search_args(paths)
|
|
28
41
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
29
42
|
expr = paths.join(" | ")
|
|
30
43
|
|
|
@@ -32,7 +45,16 @@ module Leptris::XML::Searchable
|
|
|
32
45
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
33
46
|
|
|
34
47
|
result_ptr =
|
|
35
|
-
if
|
|
48
|
+
if version
|
|
49
|
+
if ns && !ns.empty?
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"version: cannot be combined with namespace bindings " \
|
|
52
|
+
"(the versioned engine entry takes no ns set yet)"
|
|
53
|
+
end
|
|
54
|
+
Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
55
|
+
doc_ptr, context_ptr, expr,
|
|
56
|
+
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
57
|
+
elsif ns && !ns.empty?
|
|
36
58
|
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
37
59
|
else
|
|
38
60
|
Leptris::XML::FFI.leptris_xpath_eval(doc_ptr, context_ptr, expr)
|
|
@@ -64,8 +86,9 @@ module Leptris::XML::Searchable
|
|
|
64
86
|
alias_method :%, :at
|
|
65
87
|
|
|
66
88
|
def css(*args)
|
|
67
|
-
handler, ns,
|
|
89
|
+
handler, ns, version = parse_search_args(args)
|
|
68
90
|
raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
|
|
91
|
+
raise ArgumentError, "version: is XPath-only" if version
|
|
69
92
|
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
70
93
|
# Nokogiri semantics: css is receiver-relative — absolute "//"
|
|
71
94
|
# from a Document, descendant ".//" from elements and fragments.
|
|
@@ -76,8 +99,9 @@ module Leptris::XML::Searchable
|
|
|
76
99
|
end
|
|
77
100
|
|
|
78
101
|
def at_css(*args)
|
|
79
|
-
handler, ns,
|
|
102
|
+
handler, ns, version = parse_search_args(args)
|
|
80
103
|
raise ArgumentError, "namespace bindings not supported in css" if ns && !ns.empty?
|
|
104
|
+
raise ArgumentError, "version: is XPath-only" if version
|
|
81
105
|
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
82
106
|
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
83
107
|
expr = args.map { |r| Leptris::XML::CssToXPath.convert(r, prefix: prefix) }
|
|
@@ -131,6 +155,15 @@ module Leptris::XML::Searchable
|
|
|
131
155
|
end
|
|
132
156
|
end
|
|
133
157
|
|
|
158
|
+
# Trailing-argument contract (#183): a String-keyed hash is the
|
|
159
|
+
# namespace-binding channel — as is a Symbol-keyed hash (the
|
|
160
|
+
# legacy prefix vocabulary, {x: "urn:x"}); a :version key whose
|
|
161
|
+
# VALUE is a version selector is the version-selection channel
|
|
162
|
+
# (xpath("//b", version: "1.0"), mirroring leptris-py#105). A
|
|
163
|
+
# DECLARED keyword cannot carry this: the moment #xpath declares
|
|
164
|
+
# one, Ruby converts the trailing namespace hash into keywords
|
|
165
|
+
# (unknown keyword: "p") — so both channels stay positional and
|
|
166
|
+
# dispatch on key/value here.
|
|
134
167
|
def parse_search_args(args)
|
|
135
168
|
handler = args.find { |a| !a.is_a?(String) && !a.is_a?(Hash) && !a.is_a?(Symbol) }
|
|
136
169
|
args = args - [handler] if handler
|
|
@@ -143,7 +176,27 @@ module Leptris::XML::Searchable
|
|
|
143
176
|
unless vars.nil? || vars.empty?
|
|
144
177
|
raise ArgumentError, "XPath variable bindings are not supported"
|
|
145
178
|
end
|
|
146
|
-
|
|
179
|
+
# Value-based disambiguation is load-bearing: Symbol keys are
|
|
180
|
+
# also the namespace vocabulary, and bare keywords FUSE into a
|
|
181
|
+
# preceding String-keyed hash at the call site ({"p" => uri,
|
|
182
|
+
# version: "1.0"} arrives as ONE hash). A :version key with any
|
|
183
|
+
# other value stays a prefix binding.
|
|
184
|
+
version = nil
|
|
185
|
+
if ns.is_a?(Hash)
|
|
186
|
+
v = ns[:version]
|
|
187
|
+
if VERSION_SELECTORS.include?(v)
|
|
188
|
+
version = ns.delete(:version)
|
|
189
|
+
ns = nil if ns.empty?
|
|
190
|
+
elsif v.is_a?(String) && v.match?(/\A\d+\.\d+\z/)
|
|
191
|
+
# A dotted-number :version value is version INTENT — raise
|
|
192
|
+
# with the valid values rather than silently binding a
|
|
193
|
+
# prefix literally. Genuine prefix bindings carry URIs.
|
|
194
|
+
raise ArgumentError,
|
|
195
|
+
"unknown XPath version #{v.inspect} " \
|
|
196
|
+
"(valid: \"1.0\"/:xpath10, \"3.1\"/:xpath31)"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
[handler, ns, version]
|
|
147
200
|
end
|
|
148
201
|
|
|
149
202
|
def looks_like_xpath?(str)
|
data/lib/leptris/xml/xpath.rb
CHANGED
|
@@ -18,28 +18,44 @@ class Leptris::XML::XPath
|
|
|
18
18
|
|
|
19
19
|
attr_reader :expression
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# +version+ (#183, mirroring leptris-py#105): nil compiles the
|
|
22
|
+
# full-grammar expression (the default); :xpath10 / "1.0" pins
|
|
23
|
+
# the strict XPath 1.0 surface. Validated at the boundary.
|
|
24
|
+
def self.compile(expression, version: nil)
|
|
22
25
|
raw = Leptris::XML::FFI.leptris_xpath_compile(expression.to_s)
|
|
23
26
|
if raw.null?
|
|
24
27
|
raise Leptris::XML::XPathError,
|
|
25
28
|
"invalid expression: #{Leptris::XML::FFI.leptris_last_error}"
|
|
26
29
|
end
|
|
27
|
-
|
|
30
|
+
version_code =
|
|
31
|
+
Leptris::XML::Searchable.xpath_version_code(version) if version
|
|
32
|
+
new(expression.to_s, CompiledHandle.new(raw), version_code)
|
|
28
33
|
end
|
|
29
34
|
|
|
30
|
-
def initialize(expression, handle)
|
|
35
|
+
def initialize(expression, handle, version_code = nil)
|
|
31
36
|
@expression = expression
|
|
32
37
|
@handle = handle
|
|
38
|
+
@version_code = version_code
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
# Evaluates against +doc_or_element+. An optional trailing hash of
|
|
36
42
|
# namespace bindings ("prefix" => uri) routes the evaluation through
|
|
37
43
|
# the namespace-bound path, matching Searchable#xpath semantics.
|
|
44
|
+
# A compiled version pin routes through the engine's versioned
|
|
45
|
+
# entry instead of the compiled handle — there is no compiled
|
|
46
|
+
# versioned lane yet (engine gap), so the strict-1.0 surface is
|
|
47
|
+
# enforced per evaluation over the stored expression.
|
|
38
48
|
def eval(doc_or_element, ns = nil)
|
|
39
49
|
context = Leptris::XML::EvaluationContext.of(doc_or_element)
|
|
40
50
|
document = context.document
|
|
41
51
|
result_ptr =
|
|
42
|
-
if
|
|
52
|
+
if @version_code
|
|
53
|
+
raise ArgumentError,
|
|
54
|
+
"version: cannot be combined with namespace bindings " "(the versioned engine entry takes no ns set yet)" if ns && !ns.empty?
|
|
55
|
+
Leptris::XML::FFI.leptris_xpath_eval_versioned(
|
|
56
|
+
document.c_ptr, context.context_node_ptr,
|
|
57
|
+
@expression, @version_code, nil)
|
|
58
|
+
elsif ns && !ns.empty?
|
|
43
59
|
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
44
60
|
Leptris::XML::FFI.leptris_xpath_compiled_eval_ns(
|
|
45
61
|
@handle, document.c_ptr, context.context_node_ptr, set)
|