leptris 1.9.174.2-aarch64-linux → 1.9.174.4-aarch64-linux
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 +38 -0
- data/TODO.perf/19-lazy-node-pointer.md +39 -0
- data/TODO.perf/20-css-translation-cache.md +24 -0
- data/TODO.perf/21-key-memo-consult.md +20 -0
- data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
- data/TODO.perf/23-cbound-value-mutations.md +27 -0
- data/TODO.perf/24-immutable-read-lanes.md +22 -0
- data/ext/leptris/native/native.c +131 -18
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/cdata.rb +2 -2
- data/lib/leptris/xml/comment.rb +2 -2
- data/lib/leptris/xml/doc_type.rb +5 -5
- data/lib/leptris/xml/document.rb +38 -38
- data/lib/leptris/xml/document_fragment.rb +1 -1
- data/lib/leptris/xml/element.rb +70 -49
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/node.rb +42 -24
- data/lib/leptris/xml/processing_instruction.rb +7 -7
- data/lib/leptris/xml/searchable.rb +37 -4
- data/lib/leptris/xml/text.rb +8 -2
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbd33e8a70b150f3955b3a7de1a136fc90e051e3015cdb7a9ad690b3a75f001e
|
|
4
|
+
data.tar.gz: 142500c600c53293edf34edaae8c90dee05369295a6fdf6dfa4167f86d8f97b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c8389eea5c5559baf0d27944e67b928d4537036bb4ab9961a2a00c0d0805f5b70eac823684b28bd2b1d85ea7946475d1c8eef1f630f047c7e23c50d2cf55cda
|
|
7
|
+
data.tar.gz: abdc548f17f8574f4edd8b82dafea01aa8b832d078c4c453b3a661c2d659ea6d02baf506c596fbb689af218648d95ef4f87b0c61506691d7c464ea9a33206fcd
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ 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.174.4] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Eager-in-C nodeset materialization (TODO.perf/22)**: xpath
|
|
13
|
+
results materialize and free in one C pass — no
|
|
14
|
+
FFI::AutoPointer, Method object, or finalizer per call; exotic
|
|
15
|
+
kinds keep the lazy path (sentinel scan in C). Measured (load
|
|
16
|
+
~11): a 2000-node xpath set materializes at Nokogiri parity
|
|
17
|
+
(55.9µs vs 57.3µs).
|
|
18
|
+
- **C-bound value mutations (TODO.perf/23)**: `Element#name=`,
|
|
19
|
+
`#content=`, `Text#content=`, `Node#unlink` run gates + version
|
|
20
|
+
bump + engine write in one dispatch (name= 237ns, content=
|
|
21
|
+
298ns measured); readonly raises verified.
|
|
22
|
+
- **Immutable read lanes (TODO.perf/24)**: `line`/`byte_offset`
|
|
23
|
+
memoize (positions never change); `Node#document` answers a
|
|
24
|
+
constructor-precomputed ivar (the scope_owned? chain leaves
|
|
25
|
+
one of the most-called readers).
|
|
26
|
+
|
|
27
|
+
## [1.9.174.3] - 2026-09-15
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Lazy FFI::Pointer on wrappers (TODO.perf/19)**: the node's
|
|
32
|
+
canonical state is the Integer address; the Pointer
|
|
33
|
+
materializes only when read — the C construction faces stop
|
|
34
|
+
minting one per node (~200ns + one allocation each, and
|
|
35
|
+
proportionally less GC). Public `c_ptr` API and identity
|
|
36
|
+
unchanged. Measured (load 22-25): GC-amortized
|
|
37
|
+
`Document.create` ~5-6µs → ~4.0µs; `NativeNode#[]` 78ns
|
|
38
|
+
(budget 70), `#content` 45ns (budget 58 met).
|
|
39
|
+
- **CSS translation cache (TODO.perf/20)**: repeat selectors skip
|
|
40
|
+
the per-call regex translation straight to the compiled handle;
|
|
41
|
+
the single-path xpath shape no longer allocates a joined
|
|
42
|
+
String per call.
|
|
43
|
+
- **`Element#key?` consults the attribute memo (TODO.perf/21)**
|
|
44
|
+
before the engine round-trip; miss semantics unchanged.
|
|
45
|
+
|
|
8
46
|
## [1.9.174.2] - 2026-09-15
|
|
9
47
|
|
|
10
48
|
### Changed
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 19 — Lazy FFI::Pointer on wrappers
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.3)
|
|
4
|
+
|
|
5
|
+
Every binding wrapper stores an FFI::Pointer (@c_ptr), and the C
|
|
6
|
+
construction faces mint one per node via
|
|
7
|
+
rb_funcall(FFI::Pointer, :new) — ~200ns plus a Ruby object and
|
|
8
|
+
its GC visit per node, paid at creation even though most nodes'
|
|
9
|
+
Pointers are never read (the hot reads are memoized or go through
|
|
10
|
+
the address-based native faces).
|
|
11
|
+
|
|
12
|
+
Store @c_address (Integer) as the canonical truth on every
|
|
13
|
+
wrapper; `c_ptr` becomes a lazy materializer
|
|
14
|
+
(@c_ptr ||= FFI::Pointer.new(@c_address)) preserving the public
|
|
15
|
+
API. Internal call sites: `@c_ptr.address` collapses to
|
|
16
|
+
@c_address (also skipping the #address dispatch), and remaining
|
|
17
|
+
internal @c_ptr reads go through the c_ptr reader (same cost as
|
|
18
|
+
the old attr_reader). The C faces stop constructing Pointers
|
|
19
|
+
entirely — they already hold the address.
|
|
20
|
+
|
|
21
|
+
Gates: suite green both modes (a missed site fails loudly — nil
|
|
22
|
+
to FFI raises TypeError); wrapper identity and c_ptr stability
|
|
23
|
+
unchanged; creation faces drop ~200ns and one allocation per
|
|
24
|
+
node; fresh-doc build row improves accordingly.
|
|
25
|
+
|
|
26
|
+
## Outcome (1.9.174.3)
|
|
27
|
+
|
|
28
|
+
@c_address is the canonical truth on every Node wrapper; c_ptr
|
|
29
|
+
materializes lazily (@c_ptr ||= FFI::Pointer.new(@c_address)),
|
|
30
|
+
preserving the public API and identity. All internal @c_ptr
|
|
31
|
+
reads became c_ptr reader calls; the 15 @c_ptr.address sites
|
|
32
|
+
collapsed to @c_address (also skipping the #address dispatch).
|
|
33
|
+
The C construction faces (create element/text, bulk children,
|
|
34
|
+
every xpath materialization) stopped minting Pointers entirely —
|
|
35
|
+
one allocation and ~200ns saved per node, and proportionally less
|
|
36
|
+
GC. Document keeps its eager reader (documents are few).
|
|
37
|
+
Measured under host load 22-25: GC-amortized Document.create
|
|
38
|
+
~5-6µs -> ~4.0µs; NativeNode#[] 78ns (budget 70 — clear at
|
|
39
|
+
load <10), content 45ns (budget 58 met).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 20 — CSS translation cache
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.3)
|
|
4
|
+
|
|
5
|
+
Searchable#css/#at_css run CssToXPath.convert on every call —
|
|
6
|
+
pure Ruby regex/string work (~300ns+) — before the compiled-
|
|
7
|
+
expression cache (TODO.perf/15) can hit on the result. The
|
|
8
|
+
translation is a pure function of (selector, prefix); cache it
|
|
9
|
+
with the same bounded-LRU shape (64 entries, Hash#shift
|
|
10
|
+
eviction, GVL-safe), so repeat selectors skip straight to the
|
|
11
|
+
compiled handle.
|
|
12
|
+
|
|
13
|
+
Gates: css/at_css results identical; repeat-call rows improve by
|
|
14
|
+
the translation cost; the join in xpath()'s expression build is
|
|
15
|
+
also skipped for the single-String-path shape (join minted a
|
|
16
|
+
fresh String per call even for one path).
|
|
17
|
+
|
|
18
|
+
## Outcome (1.9.174.3)
|
|
19
|
+
|
|
20
|
+
Searchable.css_expression: bounded LRU (64, shared limit) keyed
|
|
21
|
+
on prefix+selector; css/at_css translate through it so repeat
|
|
22
|
+
selectors skip the regex machinery straight to the compiled-
|
|
23
|
+
expression cache. The single-String-path shape of xpath/at_xpath
|
|
24
|
+
no longer joins (join minted a fresh String per call).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 21 — key? consults the attribute memo
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.3)
|
|
4
|
+
|
|
5
|
+
Element#key?/has_attribute? pays an engine FFI round-trip per
|
|
6
|
+
call even when the versioned @attr_values memo already proves
|
|
7
|
+
presence. Consult the memo first (hit = true; only a miss falls
|
|
8
|
+
through to the engine + written-name fallback — the memo cannot
|
|
9
|
+
prove ABSENCE for a partial fill).
|
|
10
|
+
|
|
11
|
+
Gates: key? true for memoized names, unchanged miss semantics
|
|
12
|
+
(undeclared-prefix fallback intact), readonly/namespace specs
|
|
13
|
+
green.
|
|
14
|
+
|
|
15
|
+
## Outcome (1.9.174.3)
|
|
16
|
+
|
|
17
|
+
Element#key? consults the versioned @attr_values memo first —
|
|
18
|
+
presence-proven hits skip the engine round-trip; partial-fill
|
|
19
|
+
misses still fall through to the engine + written-name fallback
|
|
20
|
+
(the memo cannot prove absence).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 22 — Eager-in-C nodeset materialization (drop the per-call AutoPointer)
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
Every xpath() nodeset result pays an FFI::AutoPointer + a Method
|
|
6
|
+
object + finalizer registration (~400-600ns plus their GC) just
|
|
7
|
+
to keep the C result handle alive until the lazy NodeSet
|
|
8
|
+
materializes — and virtually every consumer materializes anyway
|
|
9
|
+
(each/size/to_a). Materialize eagerly in C instead: one face runs
|
|
10
|
+
the bulk pass AND frees the result handle, returning the Ruby
|
|
11
|
+
Array; Searchable wraps it in the eager NodeSet constructor (an
|
|
12
|
+
Array-backed set has no handle at all). Exotic node kinds (the
|
|
13
|
+
module-object sentinel) fall back to the existing lazy path with
|
|
14
|
+
the handle intact.
|
|
15
|
+
|
|
16
|
+
Gates: xpath results identical for every kind (including
|
|
17
|
+
sentinel-graded kinds); laziness is internal — the public NodeSet
|
|
18
|
+
behaviors (indexing, iteration, size) unchanged; the xpath row
|
|
19
|
+
drops the AutoPointer cost per call.
|
|
20
|
+
|
|
21
|
+
## Outcome (1.9.174.4)
|
|
22
|
+
|
|
23
|
+
Native.materialize_xpath: the bulk pass AND the result free in C;
|
|
24
|
+
Searchable's nodeset branch wraps the returned Array in the eager
|
|
25
|
+
NodeSet (no AutoPointer, no Method object, no finalizer per
|
|
26
|
+
xpath call). Sentinel scans happen in C (a Ruby include? would
|
|
27
|
+
cost microseconds on large sets) — any exotic kind returns Qnil
|
|
28
|
+
with the handle alive for the lazy fallback. Measured (load
|
|
29
|
+
~11): full materialization of a 2000-node xpath set 55.9µs vs
|
|
30
|
+
Nokogiri 57.3µs — parity on the heavy shape.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 23 — C-bound name= / content= / unlink
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
The value mutations still run ensure_writable! + FFI marshaling
|
|
6
|
+
per call (~450ns): Element#name=, Element#content=,
|
|
7
|
+
Text#content=, and Node#unlink (transforms rename, rewrite, and
|
|
8
|
+
remove nodes constantly). Same face shape as set_binding_attribute:
|
|
9
|
+
gates + version bump + engine write in one dispatch —
|
|
10
|
+
set_binding_name (element names), set_binding_text (element text
|
|
11
|
+
or text-node content, kind-dispatched in C), and
|
|
12
|
+
unlink_binding_node (plus the Ruby-side @parent clear and memo
|
|
13
|
+
invalidations that ride the version bump).
|
|
14
|
+
|
|
15
|
+
Gates: rename/content/unlink semantics unchanged (memos, sibling
|
|
16
|
+
stamps, path memo all invalidate through the version bump);
|
|
17
|
+
readonly raises through every face.
|
|
18
|
+
|
|
19
|
+
## Outcome (1.9.174.4)
|
|
20
|
+
|
|
21
|
+
Native.set_binding_name / set_binding_text (kind-dispatched
|
|
22
|
+
element-vs-text) / unlink_binding_node — gates + bump + engine
|
|
23
|
+
write in one dispatch, wired into Element#name=, #content=,
|
|
24
|
+
Text#content=, and Node#unlink (@parent clear and memo
|
|
25
|
+
invalidation ride the version bump). Measured (load ~11):
|
|
26
|
+
name= 237ns, content= 298ns, unlink+re-add 1.74µs; readonly
|
|
27
|
+
raises verified through every face.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 24 — Immutable read lanes: line/byte_offset memo + precomputed public #document
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
Node#line and #byte_offset pay an FFI round-trip per call, but a
|
|
6
|
+
node's source position never changes — memoize unconditionally
|
|
7
|
+
(first read wins). The public Node#document walks scope_owned? (a
|
|
8
|
+
method chain) on every call though scope ownership is fixed at
|
|
9
|
+
construction — precompute @pub_document once (nil for
|
|
10
|
+
scope-owned) including at every C construction site.
|
|
11
|
+
|
|
12
|
+
Gates: line/byte_offset stable across mutations (they must be —
|
|
13
|
+
the engine reports source positions); document nil for
|
|
14
|
+
scope-owned yields exactly as before; suite both modes.
|
|
15
|
+
|
|
16
|
+
## Outcome (1.9.174.4)
|
|
17
|
+
|
|
18
|
+
line/byte_offset memoize unconditionally (a node's source
|
|
19
|
+
position never changes; 40ns hits). Node#document answers the
|
|
20
|
+
constructor-precomputed @pub_document (nil for scope-owned
|
|
21
|
+
yields) — the scope_owned? method chain disappears from one of
|
|
22
|
+
the most-called readers; every C construction site sets it.
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -85,6 +85,10 @@ static xp_free_fn f_xp_free;
|
|
|
85
85
|
static first_child_fn f_first_child;
|
|
86
86
|
static node_str_fn f_comment_content, f_cdata_content, f_pi_target,
|
|
87
87
|
f_pi_data;
|
|
88
|
+
typedef int (*set_str_fn)(void *, const char *);
|
|
89
|
+
typedef int (*node_unlink_fn)(void *);
|
|
90
|
+
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
91
|
+
static node_unlink_fn f_node_unlink;
|
|
88
92
|
static set_root_fn f_set_root;
|
|
89
93
|
static doc_free_fn f_doc_free;
|
|
90
94
|
|
|
@@ -192,6 +196,10 @@ static void resolve_symbols(const char *lib_path)
|
|
|
192
196
|
f_cdata_content = (node_str_fn)lib_sym(h, "leptris_cdata_node_get_content");
|
|
193
197
|
f_pi_target = (node_str_fn)lib_sym(h, "leptris_pi_node_get_target");
|
|
194
198
|
f_pi_data = (node_str_fn)lib_sym(h, "leptris_pi_node_get_data");
|
|
199
|
+
f_elem_set_name = (set_str_fn)lib_sym(h, "leptris_element_set_name");
|
|
200
|
+
f_elem_set_text = (set_str_fn)lib_sym(h, "leptris_element_set_text");
|
|
201
|
+
f_text_set_content = (set_str_fn)lib_sym(h, "leptris_text_node_set_content");
|
|
202
|
+
f_node_unlink = (node_unlink_fn)lib_sym(h, "leptris_node_unlink");
|
|
195
203
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
196
204
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
197
205
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -201,7 +209,8 @@ static void resolve_symbols(const char *lib_path)
|
|
|
201
209
|
!f_insert_after || !f_insert_before || !f_set_attr || !f_set_root ||
|
|
202
210
|
!f_xp_type || !f_xp_free || !f_first_child ||
|
|
203
211
|
!f_comment_content || !f_cdata_content || !f_pi_target ||
|
|
204
|
-
!f_pi_data ||
|
|
212
|
+
!f_pi_data || !f_elem_set_name || !f_elem_set_text ||
|
|
213
|
+
!f_text_set_content || !f_node_unlink ||
|
|
205
214
|
!f_doc_free ||
|
|
206
215
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
207
216
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -630,6 +639,74 @@ static VALUE nf_set_binding_attribute(VALUE self, VALUE document,
|
|
|
630
639
|
return INT2FIX(st);
|
|
631
640
|
}
|
|
632
641
|
|
|
642
|
+
/* C-bound value mutations (TODO.perf/23): the same gates + bump
|
|
643
|
+
* + engine write shape as set_binding_attribute. */
|
|
644
|
+
static VALUE nf_set_binding_name(VALUE self, VALUE document,
|
|
645
|
+
VALUE addr, VALUE name)
|
|
646
|
+
{
|
|
647
|
+
void *node;
|
|
648
|
+
int st;
|
|
649
|
+
|
|
650
|
+
(void)self;
|
|
651
|
+
resolve_binding_classes();
|
|
652
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
653
|
+
rb_raise(c_use_after_free_error,
|
|
654
|
+
"owning document has been freed");
|
|
655
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
656
|
+
rb_raise(c_readonly_error,
|
|
657
|
+
"document is readonly — mutation attempted");
|
|
658
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
659
|
+
rb_ivar_set(document, id_iv_version,
|
|
660
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
661
|
+
st = f_elem_set_name(node, StringValueCStr(name));
|
|
662
|
+
return INT2FIX(st);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* Element text or text-node content — kind-dispatched in C. */
|
|
666
|
+
static VALUE nf_set_binding_text(VALUE self, VALUE document,
|
|
667
|
+
VALUE addr, VALUE content)
|
|
668
|
+
{
|
|
669
|
+
void *node;
|
|
670
|
+
int st;
|
|
671
|
+
|
|
672
|
+
(void)self;
|
|
673
|
+
resolve_binding_classes();
|
|
674
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
675
|
+
rb_raise(c_use_after_free_error,
|
|
676
|
+
"owning document has been freed");
|
|
677
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
678
|
+
rb_raise(c_readonly_error,
|
|
679
|
+
"document is readonly — mutation attempted");
|
|
680
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
681
|
+
rb_ivar_set(document, id_iv_version,
|
|
682
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
683
|
+
st = f_node_type(node) == NT_ELEMENT
|
|
684
|
+
? f_elem_set_text(node, StringValueCStr(content))
|
|
685
|
+
: f_text_set_content(node, StringValueCStr(content));
|
|
686
|
+
return INT2FIX(st);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
690
|
+
VALUE addr)
|
|
691
|
+
{
|
|
692
|
+
void *node;
|
|
693
|
+
int st;
|
|
694
|
+
|
|
695
|
+
(void)self;
|
|
696
|
+
resolve_binding_classes();
|
|
697
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
698
|
+
rb_raise(c_use_after_free_error,
|
|
699
|
+
"owning document has been freed");
|
|
700
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
701
|
+
rb_raise(c_readonly_error,
|
|
702
|
+
"document is readonly — mutation attempted");
|
|
703
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
704
|
+
rb_ivar_set(document, id_iv_version,
|
|
705
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
706
|
+
st = f_node_unlink(node);
|
|
707
|
+
return INT2FIX(st);
|
|
708
|
+
}
|
|
709
|
+
|
|
633
710
|
/* C-bound insert family (TODO.perf/14): prepend / insert-after /
|
|
634
711
|
* insert-before share append's gates + predicate + bump shape.
|
|
635
712
|
* mode: 1 prepend, 2 after, 3 before (anchor = receiver). */
|
|
@@ -870,13 +947,13 @@ static VALUE bulk_children_impl(VALUE document, VALUE parent_addr,
|
|
|
870
947
|
key = ULL2NUM((uint64_t)(uintptr_t)buf[i]);
|
|
871
948
|
node = rb_hash_aref(cache, key);
|
|
872
949
|
if (NIL_P(node)) {
|
|
873
|
-
VALUE ptr = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
874
950
|
node = rb_obj_alloc(binding_klass_for(kinds[i]));
|
|
875
|
-
rb_iv_set(node, "@
|
|
951
|
+
rb_iv_set(node, "@c_address", key);
|
|
876
952
|
rb_iv_set(node, "@document", document);
|
|
877
953
|
rb_iv_set(node, "@parent", Qnil);
|
|
878
954
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
879
955
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
956
|
+
rb_iv_set(node, "@pub_document", document);
|
|
880
957
|
rb_iv_set(node, "@node_type", INT2FIX(kinds[i]));
|
|
881
958
|
rb_hash_aset(cache, key, node);
|
|
882
959
|
}
|
|
@@ -913,15 +990,45 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
913
990
|
void *result, void *ptr, int kind,
|
|
914
991
|
int idx);
|
|
915
992
|
|
|
993
|
+
static VALUE bulk_xpath_array(VALUE document, void *result);
|
|
994
|
+
|
|
916
995
|
static VALUE nf_bulk_xpath(VALUE self, VALUE document, VALUE result_ptr_val)
|
|
996
|
+
{
|
|
997
|
+
(void)self;
|
|
998
|
+
return bulk_xpath_array(document,
|
|
999
|
+
(void *)(uintptr_t)NUM2ULL(result_ptr_val));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/* TODO.perf/22: materialize AND free — the eager xpath path owns
|
|
1003
|
+
* the handle lifecycle in C; no Ruby AutoPointer, no finalizer. */
|
|
1004
|
+
static VALUE nf_materialize_xpath(VALUE self, VALUE document,
|
|
1005
|
+
VALUE result_ptr_val)
|
|
917
1006
|
{
|
|
918
1007
|
void *result = (void *)(uintptr_t)NUM2ULL(result_ptr_val);
|
|
1008
|
+
VALUE out;
|
|
1009
|
+
long i, n;
|
|
1010
|
+
|
|
1011
|
+
(void)self;
|
|
1012
|
+
out = bulk_xpath_array(document, result);
|
|
1013
|
+
n = RARRAY_LEN(out);
|
|
1014
|
+
for (i = 0; i < n; i++) {
|
|
1015
|
+
if (rb_ary_entry(out, i) == m_native_sentinel) {
|
|
1016
|
+
/* Exotic kinds need the Ruby fallback per entry — the
|
|
1017
|
+
* caller keeps the handle and goes the lazy path. */
|
|
1018
|
+
return Qnil;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
f_xp_free(result);
|
|
1022
|
+
return out;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
static VALUE bulk_xpath_array(VALUE document, void *result)
|
|
1026
|
+
{
|
|
919
1027
|
void **buf;
|
|
920
1028
|
int *kinds;
|
|
921
1029
|
int count, i;
|
|
922
1030
|
VALUE cache, out;
|
|
923
1031
|
|
|
924
|
-
(void)self;
|
|
925
1032
|
resolve_binding_classes();
|
|
926
1033
|
size_t scount = f_xp_count(result);
|
|
927
1034
|
if (scount == 0)
|
|
@@ -965,13 +1072,13 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
965
1072
|
case 0: /* element */
|
|
966
1073
|
node = rb_hash_aref(cache, key);
|
|
967
1074
|
if (NIL_P(node)) {
|
|
968
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
969
1075
|
node = rb_obj_alloc(c_b_element);
|
|
970
|
-
rb_iv_set(node, "@
|
|
1076
|
+
rb_iv_set(node, "@c_address", key);
|
|
971
1077
|
rb_iv_set(node, "@document", document);
|
|
972
1078
|
rb_iv_set(node, "@parent", Qnil);
|
|
973
1079
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
974
1080
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1081
|
+
rb_iv_set(node, "@pub_document", document);
|
|
975
1082
|
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
976
1083
|
rb_hash_aset(cache, key, node);
|
|
977
1084
|
}
|
|
@@ -980,9 +1087,8 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
980
1087
|
{
|
|
981
1088
|
const char *name = f_xp_node_name(result, idx);
|
|
982
1089
|
const char *value = f_xp_node_value(result, idx);
|
|
983
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
984
1090
|
node = rb_obj_alloc(c_b_result_attr);
|
|
985
|
-
rb_iv_set(node, "@
|
|
1091
|
+
rb_iv_set(node, "@c_address", key);
|
|
986
1092
|
rb_iv_set(node, "@document", document);
|
|
987
1093
|
rb_iv_set(node, "@attr_name",
|
|
988
1094
|
name ? rb_utf8_str_new_cstr(name) : Qnil);
|
|
@@ -998,8 +1104,7 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
998
1104
|
if (nt == 8) { /* NODE_SYNTHETIC_TEXT */
|
|
999
1105
|
s = f_xp_node_value(result, idx);
|
|
1000
1106
|
node = rb_obj_alloc(c_b_result_text);
|
|
1001
|
-
rb_iv_set(node, "@
|
|
1002
|
-
rb_funcall(c_ffi_pointer, id_ptr_new, 1, key));
|
|
1107
|
+
rb_iv_set(node, "@c_address", key);
|
|
1003
1108
|
rb_iv_set(node, "@document", document);
|
|
1004
1109
|
rb_iv_set(node, "@value",
|
|
1005
1110
|
s ? rb_utf8_str_new_cstr(s) : Qnil);
|
|
@@ -1007,13 +1112,13 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
1007
1112
|
}
|
|
1008
1113
|
node = rb_hash_aref(cache, key);
|
|
1009
1114
|
if (NIL_P(node)) {
|
|
1010
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
1011
1115
|
node = rb_obj_alloc(binding_klass_for(nt));
|
|
1012
|
-
rb_iv_set(node, "@
|
|
1116
|
+
rb_iv_set(node, "@c_address", key);
|
|
1013
1117
|
rb_iv_set(node, "@document", document);
|
|
1014
1118
|
rb_iv_set(node, "@parent", Qnil);
|
|
1015
1119
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1016
1120
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1121
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1017
1122
|
rb_iv_set(node, "@node_type", INT2FIX(nt));
|
|
1018
1123
|
rb_hash_aset(cache, key, node);
|
|
1019
1124
|
}
|
|
@@ -1096,13 +1201,13 @@ static VALUE nf_create_binding_element(VALUE self, VALUE document,
|
|
|
1096
1201
|
if (!ptr)
|
|
1097
1202
|
return Qnil; /* caller raises with the error channel */
|
|
1098
1203
|
node = rb_obj_alloc(c_b_element);
|
|
1099
|
-
rb_iv_set(node, "@
|
|
1100
|
-
|
|
1101
|
-
ULL2NUM((uint64_t)(uintptr_t)ptr)));
|
|
1204
|
+
rb_iv_set(node, "@c_address",
|
|
1205
|
+
ULL2NUM((uint64_t)(uintptr_t)ptr));
|
|
1102
1206
|
rb_iv_set(node, "@document", document);
|
|
1103
1207
|
rb_iv_set(node, "@parent", Qnil);
|
|
1104
1208
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1105
1209
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1210
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1106
1211
|
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
1107
1212
|
cache = binding_cache_of(document);
|
|
1108
1213
|
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
@@ -1122,13 +1227,13 @@ static VALUE nf_create_binding_text(VALUE self, VALUE document,
|
|
|
1122
1227
|
if (!ptr)
|
|
1123
1228
|
return Qnil;
|
|
1124
1229
|
node = rb_obj_alloc(c_b_text);
|
|
1125
|
-
rb_iv_set(node, "@
|
|
1126
|
-
|
|
1127
|
-
ULL2NUM((uint64_t)(uintptr_t)ptr)));
|
|
1230
|
+
rb_iv_set(node, "@c_address",
|
|
1231
|
+
ULL2NUM((uint64_t)(uintptr_t)ptr));
|
|
1128
1232
|
rb_iv_set(node, "@document", document);
|
|
1129
1233
|
rb_iv_set(node, "@parent", Qnil);
|
|
1130
1234
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1131
1235
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1236
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1132
1237
|
rb_iv_set(node, "@node_type", INT2FIX(1));
|
|
1133
1238
|
cache = binding_cache_of(document);
|
|
1134
1239
|
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
@@ -1584,6 +1689,14 @@ void Init_native(void)
|
|
|
1584
1689
|
nf_insert_binding_child, 4);
|
|
1585
1690
|
rb_define_module_function(m_native, "at_xpath_first",
|
|
1586
1691
|
nf_at_xpath_first, 2);
|
|
1692
|
+
rb_define_module_function(m_native, "materialize_xpath",
|
|
1693
|
+
nf_materialize_xpath, 2);
|
|
1694
|
+
rb_define_module_function(m_native, "set_binding_name",
|
|
1695
|
+
nf_set_binding_name, 3);
|
|
1696
|
+
rb_define_module_function(m_native, "set_binding_text",
|
|
1697
|
+
nf_set_binding_text, 3);
|
|
1698
|
+
rb_define_module_function(m_native, "unlink_binding_node",
|
|
1699
|
+
nf_unlink_binding_node, 2);
|
|
1587
1700
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
1588
1701
|
nf_fast_inner_xml, 1);
|
|
1589
1702
|
rb_define_module_function(m_native, "doc_handle_attach",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/cdata.rb
CHANGED
|
@@ -6,7 +6,7 @@ class Leptris::XML::CDATA < Leptris::XML::Text
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result = Leptris::XML::FFI.leptris_cdata_node_get_content(
|
|
9
|
+
result = Leptris::XML::FFI.leptris_cdata_node_get_content(c_ptr)
|
|
10
10
|
if @document
|
|
11
11
|
@content = result
|
|
12
12
|
@content_version = @document.version
|
|
@@ -17,7 +17,7 @@ class Leptris::XML::CDATA < Leptris::XML::Text
|
|
|
17
17
|
def content=(new_content)
|
|
18
18
|
ensure_writable!
|
|
19
19
|
Leptris::XML::FFI.check_status(
|
|
20
|
-
Leptris::XML::FFI.leptris_cdata_node_set_content(
|
|
20
|
+
Leptris::XML::FFI.leptris_cdata_node_set_content(c_ptr, new_content.to_s))
|
|
21
21
|
new_content
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/leptris/xml/comment.rb
CHANGED
|
@@ -6,7 +6,7 @@ class Leptris::XML::Comment < Leptris::XML::Node
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result = Leptris::XML::FFI.leptris_comment_node_get_content(
|
|
9
|
+
result = Leptris::XML::FFI.leptris_comment_node_get_content(c_ptr)
|
|
10
10
|
if @document
|
|
11
11
|
@content = result
|
|
12
12
|
@content_version = @document.version
|
|
@@ -17,7 +17,7 @@ class Leptris::XML::Comment < Leptris::XML::Node
|
|
|
17
17
|
def content=(new_content)
|
|
18
18
|
ensure_writable!
|
|
19
19
|
Leptris::XML::FFI.check_status(
|
|
20
|
-
Leptris::XML::FFI.leptris_comment_node_set_content(
|
|
20
|
+
Leptris::XML::FFI.leptris_comment_node_set_content(c_ptr, new_content.to_s))
|
|
21
21
|
new_content
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/leptris/xml/doc_type.rb
CHANGED
|
@@ -9,24 +9,24 @@ class Leptris::XML::DocType
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def name
|
|
12
|
-
Leptris::XML::FFI.leptris_doctype_get_name(
|
|
12
|
+
Leptris::XML::FFI.leptris_doctype_get_name(c_ptr)
|
|
13
13
|
end
|
|
14
14
|
alias_method :node_name, :name
|
|
15
15
|
|
|
16
16
|
def root_name
|
|
17
|
-
Leptris::XML::FFI.leptris_doctype_get_root_name(
|
|
17
|
+
Leptris::XML::FFI.leptris_doctype_get_root_name(c_ptr)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def public_id
|
|
21
|
-
Leptris::XML::FFI.leptris_doctype_get_public_id(
|
|
21
|
+
Leptris::XML::FFI.leptris_doctype_get_public_id(c_ptr)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def system_id
|
|
25
|
-
Leptris::XML::FFI.leptris_doctype_get_system_id(
|
|
25
|
+
Leptris::XML::FFI.leptris_doctype_get_system_id(c_ptr)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def internal_subset
|
|
29
|
-
Leptris::XML::FFI.leptris_doctype_get_internal_subset(
|
|
29
|
+
Leptris::XML::FFI.leptris_doctype_get_internal_subset(c_ptr)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def external_id
|