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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd58b08303252f035517604c63e63c1db6583e84fb622c03026ea259fbbd0baa
4
- data.tar.gz: 9ebde3d10256cb54d4b091257d7cc08236c10448e8b2427251c855360bd2a1e4
3
+ metadata.gz: c649879fc6a03c38e8c36df987c079271d62583992ae6836f23f53dae102a82e
4
+ data.tar.gz: 91c1a0d1f5101f73841bfee5ca6fd4ad07bbe36a9b6035c7542d3c43bac14b7e
5
5
  SHA512:
6
- metadata.gz: ff02784b8881a170661ecfb091bc38a1c0d61cff7034b01aecef96a2773da963c2482d09d289978fc0d7c18e7a481417f82dd3d026afc0cfa41c79ef8eb2f0fd
7
- data.tar.gz: c5f9ef06098af10c1b0b63b6e878b58142f814f5f111dd697080d02591839cef3cd95ff4c6168493c498ac05c3f885a3eb85341e9819b8a2bdb15e1a4e5f5212
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.156.0"
4
+ VERSION = "1.9.156.2"
5
5
  end
@@ -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
@@ -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, _vars = parse_search_args(paths)
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 ns && !ns.empty?
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, _vars = parse_search_args(paths)
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 ns && !ns.empty?
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, _ = parse_search_args(args)
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, _ = parse_search_args(args)
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
- [handler, ns, vars]
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)
@@ -18,28 +18,44 @@ class Leptris::XML::XPath
18
18
 
19
19
  attr_reader :expression
20
20
 
21
- def self.compile(expression)
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
- new(expression.to_s, CompiledHandle.new(raw))
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 ns && !ns.empty?
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.156.0
4
+ version: 1.9.156.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.