leptris 1.9.144.1 → 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 +115 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +3 -0
- data/lib/leptris/xml/element.rb +43 -0
- data/lib/leptris/xml/ffi.rb +6 -0
- data/lib/leptris/xml/searchable.rb +60 -7
- data/lib/leptris/xml/xpath.rb +20 -4
- metadata +2 -2
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,121 @@ 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
|
+
|
|
68
|
+
## [1.9.152.1] - 2026-09-13
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **#178 (ask 1): attach seams lift in-scope namespace
|
|
73
|
+
declarations on adoption.** The engine's attach entries move a
|
|
74
|
+
subtree but not the source's in-scope declarations — a
|
|
75
|
+
cross-document (or out-of-scope) `add_child` /
|
|
76
|
+
`prepend_child` / `add_next_sibling` / `add_previous_sibling` /
|
|
77
|
+
`Document#root=` produced serialized output with UNDECLARED
|
|
78
|
+
prefixes. `Element.lift_namespaces_for_adoption` snapshots the
|
|
79
|
+
node's scope before the move and re-declares on the moved
|
|
80
|
+
element everything the target does not already resolve
|
|
81
|
+
identically (own declarations and identical target scopes add
|
|
82
|
+
nothing — same-scope moves stay output-identical).
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
|
|
86
|
+
- **#178 (ask 3): `benchmark/consumer_pipeline.rb`** — the
|
|
87
|
+
model-pipeline leg: a nested 7000-item document walked into
|
|
88
|
+
typed objects, with `GC.stat` allocation deltas (load
|
|
89
|
+
independent: ~799k allocations per run) and fresh-process
|
|
90
|
+
one-shot medians (n=9, the CLI-workload shape where YJIT never
|
|
91
|
+
amortizes).
|
|
92
|
+
|
|
93
|
+
## [1.9.156.0] - 2026-09-13
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
- **Lockstep with libleptris 1.9.156** (1.9.152 → 1.9.156; audit
|
|
98
|
+
290/290 — new symbol `leptris_element_create_child` attached):
|
|
99
|
+
pulls in the leptris/leptris#1038 heap-corruption fix — stale
|
|
100
|
+
root-doc map entries (pool-fallback creates + XInclude
|
|
101
|
+
adopted-child free) resolved freed documents after address
|
|
102
|
+
recycling, corrupting the heap in ~5-10% of long-running
|
|
103
|
+
processes; entries now die with their document. 1.9.152 and
|
|
104
|
+
1.9.152.1 shipped inside the affected range (1.9.151-154).
|
|
105
|
+
Also rides: WHATWG `<template>` cgroup drops and `</template>`
|
|
106
|
+
pop-through (html5lib corpus 1200 → 1206), frame/frameset
|
|
107
|
+
in-template drops (1.9.154), and the lane-18 DOM perf rows.
|
|
108
|
+
|
|
109
|
+
## [1.9.152.0] - 2026-09-13
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
|
|
113
|
+
- **Lockstep with libleptris 1.9.152** (1.9.144 → 1.9.152; no new
|
|
114
|
+
C surface — audit 289/289): the lane-18 DOM mutation performance
|
|
115
|
+
arc rides the binding directly — attr-heavy parse up to 2.7x
|
|
116
|
+
faster (1.9.147/1.9.149), the DOM create path 91.6 → 6.6 ns/op
|
|
117
|
+
and the append row 401 → ~258 µs (1.9.150–1.9.152), set-attr row
|
|
118
|
+
390 → 262 µs. WHATWG HTML: `<template>` insertion modes
|
|
119
|
+
(1.9.146/1.9.148) — an open template is a scope boundary for
|
|
120
|
+
table-context starts, and each open template tracks its saved
|
|
121
|
+
insertion mode (implied tr/tbody/rows per WHATWG 13.2.6.4.10).
|
|
122
|
+
|
|
8
123
|
## [1.9.144.1] - 2026-09-12
|
|
9
124
|
|
|
10
125
|
### Fixed
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.156"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -244,6 +244,9 @@ class Leptris::XML::Document
|
|
|
244
244
|
# document's pool until #free).
|
|
245
245
|
def root=(element)
|
|
246
246
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
247
|
+
# A document root has no in-scope declarations of its own —
|
|
248
|
+
# lift everything the element's source scope carried (#178).
|
|
249
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(element, {})
|
|
247
250
|
Leptris::XML::FFI.check_status(
|
|
248
251
|
Leptris::XML::FFI.leptris_document_set_root(@c_ptr, element.c_ptr))
|
|
249
252
|
@version += 1
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -215,8 +215,35 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
215
215
|
p ? "#{p}:#{name}" : name
|
|
216
216
|
end
|
|
217
217
|
|
|
218
|
+
# Cross-document and cross-scope adoption (#178 ask 1): the
|
|
219
|
+
# engine's attach entries move a subtree but do not lift the
|
|
220
|
+
# source's in-scope namespace declarations, so serialized output
|
|
221
|
+
# can carry undeclared prefixes. Re-declares on +node+ everything
|
|
222
|
+
# from its in-scope set that +target_scope+ (the attach target's
|
|
223
|
+
# in-scope map) does not already resolve identically — the
|
|
224
|
+
# declarations ride the moved element, staying local to the
|
|
225
|
+
# adopted subtree. Call BEFORE attaching: the source scope is
|
|
226
|
+
# read through the node's current ancestors. Identical scopes
|
|
227
|
+
# (the common same-document move) add nothing.
|
|
228
|
+
def self.lift_namespaces_for_adoption(node, target_scope)
|
|
229
|
+
return unless node.is_a?(Leptris::XML::Element)
|
|
230
|
+
# Declarations the node already carries (its own definitions —
|
|
231
|
+
# preserved by the C copy and by moves) ride it as-is; lifting
|
|
232
|
+
# must not duplicate them, only what came from ANCESTORS.
|
|
233
|
+
own = node.namespace_definitions.each_with_object({}) do |ns, h|
|
|
234
|
+
h[ns.prefix ? "xmlns:#{ns.prefix}" : "xmlns"] = ns.href
|
|
235
|
+
end
|
|
236
|
+
node.namespaces.each do |key, uri|
|
|
237
|
+
next if target_scope[key] == uri || own[key] == uri
|
|
238
|
+
prefix = key.start_with?("xmlns:") ? key.delete_prefix("xmlns:") : nil
|
|
239
|
+
node.add_namespace_definition(prefix, uri)
|
|
240
|
+
end
|
|
241
|
+
nil
|
|
242
|
+
end
|
|
243
|
+
|
|
218
244
|
def prepend_child(node)
|
|
219
245
|
ensure_writable!
|
|
246
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
220
247
|
Leptris::XML::FFI.check_status(
|
|
221
248
|
Leptris::XML::FFI.leptris_element_prepend_child(@c_ptr, node.c_ptr))
|
|
222
249
|
node
|
|
@@ -224,6 +251,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
224
251
|
|
|
225
252
|
def add_next_sibling(node)
|
|
226
253
|
ensure_writable!
|
|
254
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
227
255
|
Leptris::XML::FFI.check_status(
|
|
228
256
|
Leptris::XML::FFI.leptris_element_insert_after(@c_ptr, node.c_ptr))
|
|
229
257
|
node
|
|
@@ -231,6 +259,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
231
259
|
|
|
232
260
|
def add_previous_sibling(node)
|
|
233
261
|
ensure_writable!
|
|
262
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
234
263
|
Leptris::XML::FFI.check_status(
|
|
235
264
|
Leptris::XML::FFI.leptris_element_insert_before(@c_ptr, node.c_ptr))
|
|
236
265
|
node
|
|
@@ -302,10 +331,24 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
302
331
|
end
|
|
303
332
|
alias_method :clone, :dup
|
|
304
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
|
+
|
|
305
347
|
def add_child(node_or_markup)
|
|
306
348
|
ensure_writable!
|
|
307
349
|
case node_or_markup
|
|
308
350
|
when Leptris::XML::Node
|
|
351
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(node_or_markup, namespaces)
|
|
309
352
|
Leptris::XML::FFI.check_status(
|
|
310
353
|
Leptris::XML::FFI.leptris_element_append_child(@c_ptr, node_or_markup.c_ptr))
|
|
311
354
|
node_or_markup
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -435,6 +435,8 @@ attach_function :leptris_parse_string,
|
|
|
435
435
|
|
|
436
436
|
attach_function :leptris_element_create,
|
|
437
437
|
[:leptris_document, :string], :leptris_element
|
|
438
|
+
attach_function :leptris_element_create_child,
|
|
439
|
+
[:leptris_element, :string], :leptris_element
|
|
438
440
|
attach_function :leptris_element_set_name,
|
|
439
441
|
[:leptris_element, :string], :leptris_status
|
|
440
442
|
attach_function :leptris_element_set_text,
|
|
@@ -455,6 +457,10 @@ attach_function :leptris_parse_string,
|
|
|
455
457
|
[:leptris_element, :string], :leptris_status
|
|
456
458
|
attach_function :leptris_element_append_child,
|
|
457
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
|
|
458
464
|
attach_function :leptris_element_prepend_child,
|
|
459
465
|
[:leptris_element, :leptris_element], :leptris_status
|
|
460
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)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.156.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|