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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd58b08303252f035517604c63e63c1db6583e84fb622c03026ea259fbbd0baa
4
- data.tar.gz: 9ebde3d10256cb54d4b091257d7cc08236c10448e8b2427251c855360bd2a1e4
3
+ metadata.gz: 2644772a66a1e58e517fe5b5e2b2fb417ac1592863f3de375d1659ecebfeaec6
4
+ data.tar.gz: ee478cc9f2387ff6527a680c37817d1c650bfe50c0e9c891768784db396cc29c
5
5
  SHA512:
6
- metadata.gz: ff02784b8881a170661ecfb091bc38a1c0d61cff7034b01aecef96a2773da963c2482d09d289978fc0d7c18e7a481417f82dd3d026afc0cfa41c79ef8eb2f0fd
7
- data.tar.gz: c5f9ef06098af10c1b0b63b6e878b58142f814f5f111dd697080d02591839cef3cd95ff4c6168493c498ac05c3f885a3eb85341e9819b8a2bdb15e1a4e5f5212
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.156"
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
@@ -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.160.0"
5
5
  end
@@ -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
@@ -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
@@ -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, _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.160.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.