leptris 1.9.156.0 → 1.9.160.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 +79 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +13 -0
- data/lib/leptris/xml/element.rb +13 -0
- data/lib/leptris/xml/ffi.rb +8 -2
- 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: 2644772a66a1e58e517fe5b5e2b2fb417ac1592863f3de375d1659ecebfeaec6
|
|
4
|
+
data.tar.gz: ee478cc9f2387ff6527a680c37817d1c650bfe50c0e9c891768784db396cc29c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e6dfa835f5704689bf7fa50bfac557bf4637b556b9f84f6ae437cf020bafe09cb42c866692d159da8f11ed0a8bad9439d13639f4c6380159d9d0c2a75600d42
|
|
7
|
+
data.tar.gz: 33558c5c76bbc5130b52e4d4d2258f4918e9c101e18494772d29a55ad758ab089b98753d9ca6cea9cd0ca7ae09107d952c701d6dcdcbdb7557a0c3b13ea7e067
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,85 @@ 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.160.0] - 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Lockstep with libleptris 1.9.160** (1.9.156 → 1.9.160; audit
|
|
13
|
+
290 → 291): the lane-18 engine performance arc — one TLS read
|
|
14
|
+
per document resolution (under nearly every API call; 1.9.158),
|
|
15
|
+
inline tree-edge encoding and register promotion in the parse
|
|
16
|
+
loop, inline int32 edge codecs, document-free back to
|
|
17
|
+
O(this document) (1.9.159), split_qname colon gate, shared edge
|
|
18
|
+
tables for parent reads, name carve, one-strlen attribute find
|
|
19
|
+
(1.9.160).
|
|
20
|
+
- **`Document#add_comment(content)`** (libleptris 1.9.160, #1032):
|
|
21
|
+
the document-level comment writer (epilog append, the #add_pi
|
|
22
|
+
twin) — parsed document-level comments round-tripped since
|
|
23
|
+
1.9.3; this closes the creation gap.
|
|
24
|
+
- Fixed a duplicate `leptris_element_create_child` attachment
|
|
25
|
+
left by the stacked 1.9.156 lockstep commits (audit silently
|
|
26
|
+
deduplicated; now clean at 291/291).
|
|
27
|
+
\n## [1.9.156.2] - 2026-09-13
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **#183: XPath version selection** (mirroring leptris-py#105):
|
|
32
|
+
`xpath`/`at_xpath` take a trailing `version:` selector —
|
|
33
|
+
`doc.xpath("count(//b)", version: "1.0")` evaluates through the
|
|
34
|
+
strict 1.0 engine lane (3.x syntax raises), `"3.1"` the full
|
|
35
|
+
grammar; `XPath.compile(expr, version:)` pins compiled
|
|
36
|
+
expressions the same way. Carried positionally — a declared
|
|
37
|
+
keyword is impossible without breaking the namespace-binding
|
|
38
|
+
hash channel (Ruby converts a trailing String-keyed hash into
|
|
39
|
+
keywords the moment one exists), so a `:version` key whose
|
|
40
|
+
VALUE is a version selector ("1.0"/"3.1"/:xpath10/:xpath31)
|
|
41
|
+
selects the channel; dotted-number values raise at the boundary
|
|
42
|
+
with the valid values; any other value stays a prefix binding
|
|
43
|
+
(a prefix literally named "version" keeps working). `version:`
|
|
44
|
+
combined with namespace bindings raises (the versioned engine
|
|
45
|
+
entry takes no ns set yet), as does `version:` on css. The nil
|
|
46
|
+
default keeps the existing eval path verbatim.
|
|
47
|
+
\n## [1.9.156.1] - 2026-09-13
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **`Element#create_child(name)`** — the fused create+append
|
|
52
|
+
(libleptris 1.9.153 `leptris_element_create_child`, audit 290)
|
|
53
|
+
landed on main seconds after the 1.9.156.0 platform gems were
|
|
54
|
+
built and published, so that release ships without the binding
|
|
55
|
+
face. This patch release carries it: one C call, tree semantics
|
|
56
|
+
identical to `create_element` + `add_child`, the builder-shape
|
|
57
|
+
single call.
|
|
58
|
+
|
|
59
|
+
## [1.9.156.0] - 2026-09-13
|
|
60
|
+
|
|
61
|
+
### Security/correctness
|
|
62
|
+
|
|
63
|
+
- **Lockstep with libleptris 1.9.156 (1.9.152 → 1.9.156)** — carries
|
|
64
|
+
the **#1038 heap-corruption fix** (engine regression window:
|
|
65
|
+
libleptris 1.9.151–1.9.155; root-doc map entries outlived their
|
|
66
|
+
document and wrote through freed pools — roaming crashes in
|
|
67
|
+
downstream binding suites at ~5% of runs). The two gem versions
|
|
68
|
+
that embedded the affected engine, **1.9.152.0 and 1.9.152.1,
|
|
69
|
+
are YANKED**; upgrade to 1.9.156.0.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **`Element#create_child(name)`** (libleptris 1.9.153): the fused
|
|
74
|
+
create+append — one document resolution, one C call, tree
|
|
75
|
+
semantics identical to `create_element` + `add_child`; the
|
|
76
|
+
builder-shape single call.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- Engine: WHATWG template-mode gaps closed (1.9.154/1.9.155):
|
|
81
|
+
frame/frameset drops inside templates, in-column-group token
|
|
82
|
+
gating, `</template>` inside `<select>`, foreign-content
|
|
83
|
+
template end-tag matching — html5lib corpus 1197 → 1206;
|
|
84
|
+
Nokogiri parity floor held. Audit 289 → 290
|
|
85
|
+
(leptris_element_create_child).
|
|
86
|
+
|
|
8
87
|
## [1.9.152.1] - 2026-09-13
|
|
9
88
|
|
|
10
89
|
### 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.160"
|
|
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
|
@@ -416,6 +416,19 @@ class Leptris::XML::Document
|
|
|
416
416
|
self
|
|
417
417
|
end
|
|
418
418
|
|
|
419
|
+
# Append a document-level comment (epilog position, after the
|
|
420
|
+
# root element) — the add_pi twin (libleptris 1.9.160, #1032;
|
|
421
|
+
# parsed document-level comments round-tripped since 1.9.3/#578,
|
|
422
|
+
# this is the writer). Returns self.
|
|
423
|
+
def add_comment(content)
|
|
424
|
+
witness = Leptris::XML::FFI.leptris_document_add_comment(
|
|
425
|
+
@c_ptr, content.to_s)
|
|
426
|
+
raise Leptris::XML::Error,
|
|
427
|
+
"leptris_document_add_comment failed" if witness.null?
|
|
428
|
+
@version += 1
|
|
429
|
+
self
|
|
430
|
+
end
|
|
431
|
+
|
|
419
432
|
# Marks the document read-only: tree mutations raise
|
|
420
433
|
# Leptris::XML::ReadOnlyError, and read paths memoize aggressively
|
|
421
434
|
# (names, content, children, attributes) since they can never go
|
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
|
@@ -220,6 +220,10 @@ attach_function :leptris_parse_string,
|
|
|
220
220
|
[:leptris_document, :string, :size_t], :leptris_node_ref
|
|
221
221
|
attach_function :leptris_document_add_pi,
|
|
222
222
|
[:leptris_document, :string, :string], :pointer
|
|
223
|
+
# Document-level comment writer (libleptris 1.9.160, #1032):
|
|
224
|
+
# epilog append, the add_pi shape.
|
|
225
|
+
attach_function :leptris_document_add_comment,
|
|
226
|
+
[:leptris_document, :string], :leptris_node_ref
|
|
223
227
|
# Document-level comments (libleptris 1.9.3, #578): parsed
|
|
224
228
|
# <!-- ... --> outside the root, prolog then epilog, in
|
|
225
229
|
# document order. Read-only surface (no add_comment yet).
|
|
@@ -435,8 +439,6 @@ attach_function :leptris_parse_string,
|
|
|
435
439
|
|
|
436
440
|
attach_function :leptris_element_create,
|
|
437
441
|
[:leptris_document, :string], :leptris_element
|
|
438
|
-
attach_function :leptris_element_create_child,
|
|
439
|
-
[:leptris_element, :string], :leptris_element
|
|
440
442
|
attach_function :leptris_element_set_name,
|
|
441
443
|
[:leptris_element, :string], :leptris_status
|
|
442
444
|
attach_function :leptris_element_set_text,
|
|
@@ -457,6 +459,10 @@ attach_function :leptris_parse_string,
|
|
|
457
459
|
[:leptris_element, :string], :leptris_status
|
|
458
460
|
attach_function :leptris_element_append_child,
|
|
459
461
|
[:leptris_element, :leptris_element], :leptris_status
|
|
462
|
+
# Fused create+append (libleptris 1.9.153): one document
|
|
463
|
+
# resolution, identical tree semantics to the two-call pair.
|
|
464
|
+
attach_function :leptris_element_create_child,
|
|
465
|
+
[:leptris_element, :string], :leptris_element
|
|
460
466
|
attach_function :leptris_element_prepend_child,
|
|
461
467
|
[:leptris_element, :leptris_element], :leptris_status
|
|
462
468
|
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)
|