leptris 1.9.174.2 → 1.9.174.3
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 +19 -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/ext/leptris/native/native.c +9 -16
- 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 +58 -49
- data/lib/leptris/xml/node.rb +29 -23
- data/lib/leptris/xml/processing_instruction.rb +7 -7
- data/lib/leptris/xml/searchable.rb +28 -4
- data/lib/leptris/xml/text.rb +2 -2
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 741e98553c4d88169d4c2f8ec0fb793eeebd80ffd8b00771fcfb0be218721f64
|
|
4
|
+
data.tar.gz: 5e02a23549a4e1c708dc40710b499e64fcd83150772dd08c8bbeac7fc77f0718
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1a3b25cd5b770b6edfb06bc2d448dacd691dc7e5af3fb301454fb1057e419c5fc779e4c9556a3f3b4be654ea5ec36be8f8e5c4e0f7ab16af380b6986da3bcb5
|
|
7
|
+
data.tar.gz: 85e81207c2abaca9a80af5dcfb33ae4e4f83c2ec24950b16d38fcb239bf7fa4d01b40d4d88b7fa19e019ba35a9ce58984c823e3f049fc47492459ebde53aa47b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.3] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Lazy FFI::Pointer on wrappers (TODO.perf/19)**: the node's
|
|
13
|
+
canonical state is the Integer address; the Pointer
|
|
14
|
+
materializes only when read — the C construction faces stop
|
|
15
|
+
minting one per node (~200ns + one allocation each, and
|
|
16
|
+
proportionally less GC). Public `c_ptr` API and identity
|
|
17
|
+
unchanged. Measured (load 22-25): GC-amortized
|
|
18
|
+
`Document.create` ~5-6µs → ~4.0µs; `NativeNode#[]` 78ns
|
|
19
|
+
(budget 70), `#content` 45ns (budget 58 met).
|
|
20
|
+
- **CSS translation cache (TODO.perf/20)**: repeat selectors skip
|
|
21
|
+
the per-call regex translation straight to the compiled handle;
|
|
22
|
+
the single-path xpath shape no longer allocates a joined
|
|
23
|
+
String per call.
|
|
24
|
+
- **`Element#key?` consults the attribute memo (TODO.perf/21)**
|
|
25
|
+
before the engine round-trip; miss semantics unchanged.
|
|
26
|
+
|
|
8
27
|
## [1.9.174.2] - 2026-09-15
|
|
9
28
|
|
|
10
29
|
### 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).
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -870,9 +870,8 @@ static VALUE bulk_children_impl(VALUE document, VALUE parent_addr,
|
|
|
870
870
|
key = ULL2NUM((uint64_t)(uintptr_t)buf[i]);
|
|
871
871
|
node = rb_hash_aref(cache, key);
|
|
872
872
|
if (NIL_P(node)) {
|
|
873
|
-
VALUE ptr = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
874
873
|
node = rb_obj_alloc(binding_klass_for(kinds[i]));
|
|
875
|
-
rb_iv_set(node, "@
|
|
874
|
+
rb_iv_set(node, "@c_address", key);
|
|
876
875
|
rb_iv_set(node, "@document", document);
|
|
877
876
|
rb_iv_set(node, "@parent", Qnil);
|
|
878
877
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
@@ -965,9 +964,8 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
965
964
|
case 0: /* element */
|
|
966
965
|
node = rb_hash_aref(cache, key);
|
|
967
966
|
if (NIL_P(node)) {
|
|
968
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
969
967
|
node = rb_obj_alloc(c_b_element);
|
|
970
|
-
rb_iv_set(node, "@
|
|
968
|
+
rb_iv_set(node, "@c_address", key);
|
|
971
969
|
rb_iv_set(node, "@document", document);
|
|
972
970
|
rb_iv_set(node, "@parent", Qnil);
|
|
973
971
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
@@ -980,9 +978,8 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
980
978
|
{
|
|
981
979
|
const char *name = f_xp_node_name(result, idx);
|
|
982
980
|
const char *value = f_xp_node_value(result, idx);
|
|
983
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
984
981
|
node = rb_obj_alloc(c_b_result_attr);
|
|
985
|
-
rb_iv_set(node, "@
|
|
982
|
+
rb_iv_set(node, "@c_address", key);
|
|
986
983
|
rb_iv_set(node, "@document", document);
|
|
987
984
|
rb_iv_set(node, "@attr_name",
|
|
988
985
|
name ? rb_utf8_str_new_cstr(name) : Qnil);
|
|
@@ -998,8 +995,7 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
998
995
|
if (nt == 8) { /* NODE_SYNTHETIC_TEXT */
|
|
999
996
|
s = f_xp_node_value(result, idx);
|
|
1000
997
|
node = rb_obj_alloc(c_b_result_text);
|
|
1001
|
-
rb_iv_set(node, "@
|
|
1002
|
-
rb_funcall(c_ffi_pointer, id_ptr_new, 1, key));
|
|
998
|
+
rb_iv_set(node, "@c_address", key);
|
|
1003
999
|
rb_iv_set(node, "@document", document);
|
|
1004
1000
|
rb_iv_set(node, "@value",
|
|
1005
1001
|
s ? rb_utf8_str_new_cstr(s) : Qnil);
|
|
@@ -1007,9 +1003,8 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
1007
1003
|
}
|
|
1008
1004
|
node = rb_hash_aref(cache, key);
|
|
1009
1005
|
if (NIL_P(node)) {
|
|
1010
|
-
VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
|
|
1011
1006
|
node = rb_obj_alloc(binding_klass_for(nt));
|
|
1012
|
-
rb_iv_set(node, "@
|
|
1007
|
+
rb_iv_set(node, "@c_address", key);
|
|
1013
1008
|
rb_iv_set(node, "@document", document);
|
|
1014
1009
|
rb_iv_set(node, "@parent", Qnil);
|
|
1015
1010
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
@@ -1096,9 +1091,8 @@ static VALUE nf_create_binding_element(VALUE self, VALUE document,
|
|
|
1096
1091
|
if (!ptr)
|
|
1097
1092
|
return Qnil; /* caller raises with the error channel */
|
|
1098
1093
|
node = rb_obj_alloc(c_b_element);
|
|
1099
|
-
rb_iv_set(node, "@
|
|
1100
|
-
|
|
1101
|
-
ULL2NUM((uint64_t)(uintptr_t)ptr)));
|
|
1094
|
+
rb_iv_set(node, "@c_address",
|
|
1095
|
+
ULL2NUM((uint64_t)(uintptr_t)ptr));
|
|
1102
1096
|
rb_iv_set(node, "@document", document);
|
|
1103
1097
|
rb_iv_set(node, "@parent", Qnil);
|
|
1104
1098
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
@@ -1122,9 +1116,8 @@ static VALUE nf_create_binding_text(VALUE self, VALUE document,
|
|
|
1122
1116
|
if (!ptr)
|
|
1123
1117
|
return Qnil;
|
|
1124
1118
|
node = rb_obj_alloc(c_b_text);
|
|
1125
|
-
rb_iv_set(node, "@
|
|
1126
|
-
|
|
1127
|
-
ULL2NUM((uint64_t)(uintptr_t)ptr)));
|
|
1119
|
+
rb_iv_set(node, "@c_address",
|
|
1120
|
+
ULL2NUM((uint64_t)(uintptr_t)ptr));
|
|
1128
1121
|
rb_iv_set(node, "@document", document);
|
|
1129
1122
|
rb_iv_set(node, "@parent", Qnil);
|
|
1130
1123
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
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
|
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -32,9 +32,9 @@ class Leptris::XML::Document
|
|
|
32
32
|
|
|
33
33
|
def initialize(c_ptr = nil, freed = Freed.new(:alive))
|
|
34
34
|
@c_ptr = c_ptr
|
|
35
|
-
# Plain-Integer address twin of
|
|
35
|
+
# Plain-Integer address twin of c_ptr: the native layer reads
|
|
36
36
|
# this ivar directly (no method dispatch), nil'ed exactly when
|
|
37
|
-
#
|
|
37
|
+
# c_ptr is.
|
|
38
38
|
@c_address = c_ptr&.address
|
|
39
39
|
@freed = freed
|
|
40
40
|
@readonly = false
|
|
@@ -209,13 +209,13 @@ class Leptris::XML::Document
|
|
|
209
209
|
|
|
210
210
|
def root
|
|
211
211
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
212
|
-
return nil if
|
|
212
|
+
return nil if c_ptr.nil?
|
|
213
213
|
# Version-stamped memo (TODO.perf/13): root changes only
|
|
214
214
|
# through mutations that advance @version (root=, unlink) or
|
|
215
|
-
# #free (which nils
|
|
215
|
+
# #free (which nils c_ptr above) — the entry point of every
|
|
216
216
|
# pipeline stops paying FFI + wrap per call.
|
|
217
217
|
return @root if @root_version == @version
|
|
218
|
-
ptr = Leptris::XML::FFI.leptris_document_root(
|
|
218
|
+
ptr = Leptris::XML::FFI.leptris_document_root(c_ptr)
|
|
219
219
|
result = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, self)
|
|
220
220
|
@root = result
|
|
221
221
|
@root_version = @version
|
|
@@ -231,7 +231,7 @@ class Leptris::XML::Document
|
|
|
231
231
|
def node
|
|
232
232
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
233
233
|
@node ||= Leptris::XML::Node.wrap(
|
|
234
|
-
Leptris::XML::FFI.leptris_document_node(
|
|
234
|
+
Leptris::XML::FFI.leptris_document_node(c_ptr), self)
|
|
235
235
|
end
|
|
236
236
|
|
|
237
237
|
# The document's children, via the document node: prolog
|
|
@@ -250,7 +250,7 @@ class Leptris::XML::Document
|
|
|
250
250
|
doc_node = node
|
|
251
251
|
return Leptris::XML::NodeSet.new(self, []) if doc_node.nil?
|
|
252
252
|
kids = doc_node.children.to_a
|
|
253
|
-
root_ptr = Leptris::XML::FFI.leptris_document_root(
|
|
253
|
+
root_ptr = Leptris::XML::FFI.leptris_document_root(c_ptr)
|
|
254
254
|
return kids if root_ptr.null?
|
|
255
255
|
return kids if kids.any? { |child| child.c_ptr == root_ptr }
|
|
256
256
|
root = Leptris::XML::Node.wrap(root_ptr, self)
|
|
@@ -289,7 +289,7 @@ class Leptris::XML::Document
|
|
|
289
289
|
Leptris::XML::Element.lift_namespaces_for_adoption(element, {})
|
|
290
290
|
end
|
|
291
291
|
Leptris::XML::FFI.check_status(
|
|
292
|
-
Leptris::XML::FFI.leptris_document_set_root(
|
|
292
|
+
Leptris::XML::FFI.leptris_document_set_root(c_ptr, element.c_ptr))
|
|
293
293
|
@version += 1
|
|
294
294
|
# Seed the root memo through wrap: a cross-document element
|
|
295
295
|
# must enter THIS document's identity cache with @document
|
|
@@ -308,7 +308,7 @@ class Leptris::XML::Document
|
|
|
308
308
|
raise Leptris::XML::Error, "leptris_element_create failed" if node.nil?
|
|
309
309
|
return node
|
|
310
310
|
end
|
|
311
|
-
ptr = Leptris::XML::FFI.leptris_element_create(
|
|
311
|
+
ptr = Leptris::XML::FFI.leptris_element_create(c_ptr, name)
|
|
312
312
|
raise Leptris::XML::Error, "leptris_element_create failed" if ptr.null?
|
|
313
313
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_ELEMENT)
|
|
314
314
|
end
|
|
@@ -319,25 +319,25 @@ class Leptris::XML::Document
|
|
|
319
319
|
raise Leptris::XML::Error, "leptris_text_node_create failed" if node.nil?
|
|
320
320
|
return node
|
|
321
321
|
end
|
|
322
|
-
ptr = Leptris::XML::FFI.leptris_text_node_create(
|
|
322
|
+
ptr = Leptris::XML::FFI.leptris_text_node_create(c_ptr, content.to_s)
|
|
323
323
|
raise Leptris::XML::Error, "leptris_text_node_create failed" if ptr.null?
|
|
324
324
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_TEXT)
|
|
325
325
|
end
|
|
326
326
|
|
|
327
327
|
def create_comment(content)
|
|
328
|
-
ptr = Leptris::XML::FFI.leptris_comment_node_create(
|
|
328
|
+
ptr = Leptris::XML::FFI.leptris_comment_node_create(c_ptr, content.to_s)
|
|
329
329
|
raise Leptris::XML::Error, "leptris_comment_node_create failed" if ptr.null?
|
|
330
330
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_COMMENT)
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
def create_cdata(content)
|
|
334
|
-
ptr = Leptris::XML::FFI.leptris_cdata_node_create(
|
|
334
|
+
ptr = Leptris::XML::FFI.leptris_cdata_node_create(c_ptr, content.to_s)
|
|
335
335
|
raise Leptris::XML::Error, "leptris_cdata_node_create failed" if ptr.null?
|
|
336
336
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_CDATA)
|
|
337
337
|
end
|
|
338
338
|
|
|
339
339
|
def create_processing_instruction(target, data = "")
|
|
340
|
-
ptr = Leptris::XML::FFI.leptris_pi_node_create(
|
|
340
|
+
ptr = Leptris::XML::FFI.leptris_pi_node_create(c_ptr, target.to_s, data.to_s)
|
|
341
341
|
raise Leptris::XML::Error, "leptris_pi_node_create failed" if ptr.null?
|
|
342
342
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_PI)
|
|
343
343
|
end
|
|
@@ -347,14 +347,14 @@ class Leptris::XML::Document
|
|
|
347
347
|
end
|
|
348
348
|
|
|
349
349
|
def dup
|
|
350
|
-
raw = Leptris::XML::FFI.leptris_document_copy(
|
|
350
|
+
raw = Leptris::XML::FFI.leptris_document_copy(c_ptr)
|
|
351
351
|
raise Leptris::XML::Error, "leptris_document_copy failed" if raw.null?
|
|
352
352
|
self.class.wrap(raw)
|
|
353
353
|
end
|
|
354
354
|
alias_method :clone, :dup
|
|
355
355
|
|
|
356
356
|
def doctype
|
|
357
|
-
ptr = Leptris::XML::FFI.leptris_document_internal_subset(
|
|
357
|
+
ptr = Leptris::XML::FFI.leptris_document_internal_subset(c_ptr)
|
|
358
358
|
return nil if ptr.null?
|
|
359
359
|
Leptris::XML::DocType.new(ptr, self)
|
|
360
360
|
end
|
|
@@ -368,18 +368,18 @@ class Leptris::XML::Document
|
|
|
368
368
|
# display-oriented and not round-trip-guaranteed).
|
|
369
369
|
def to_xml(indent: 0, no_decl: false, encoding: nil, indent_text: false)
|
|
370
370
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
371
|
-
return "" if
|
|
371
|
+
return "" if c_ptr.nil?
|
|
372
372
|
case indent_text
|
|
373
373
|
when String
|
|
374
374
|
return Leptris::XML::Serialization.to_xml_indent_unit(
|
|
375
|
-
|
|
375
|
+
c_ptr, indent_text, indent: indent, no_decl: no_decl,
|
|
376
376
|
encoding: encoding)
|
|
377
377
|
when true
|
|
378
378
|
return Leptris::XML::Serialization.to_xml_display(
|
|
379
|
-
|
|
379
|
+
c_ptr, indent: indent, no_decl: no_decl, encoding: encoding)
|
|
380
380
|
end
|
|
381
381
|
Leptris::XML::Serialization.to_xml(
|
|
382
|
-
Leptris::XML::Serialization::DOCUMENT_SERIALIZE_INTO,
|
|
382
|
+
Leptris::XML::Serialization::DOCUMENT_SERIALIZE_INTO, c_ptr,
|
|
383
383
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
384
384
|
end
|
|
385
385
|
alias_method :to_s, :to_xml
|
|
@@ -391,7 +391,7 @@ class Leptris::XML::Document
|
|
|
391
391
|
no_decl: opts.fetch(:no_decl, false),
|
|
392
392
|
encoding: opts[:encoding])
|
|
393
393
|
status = Leptris::XML::FFI.leptris_document_save_file(
|
|
394
|
-
|
|
394
|
+
c_ptr, path, opts_struct.pointer)
|
|
395
395
|
Leptris::XML::FFI.check_status(status)
|
|
396
396
|
self
|
|
397
397
|
end
|
|
@@ -402,11 +402,11 @@ class Leptris::XML::Document
|
|
|
402
402
|
exclusive: false,
|
|
403
403
|
mode: nil)
|
|
404
404
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
405
|
-
return "" if
|
|
405
|
+
return "" if c_ptr.nil?
|
|
406
406
|
resolved_mode = mode || (exclusive ? Leptris::XML::FFI::C14N_MODE_EXCLUSIVE
|
|
407
407
|
: Leptris::XML::FFI::C14N_MODE_CANONICAL)
|
|
408
408
|
Leptris::XML::Serialization.canonicalize(
|
|
409
|
-
Leptris::XML::FFI.method(:leptris_c14n_canonicalize_ex),
|
|
409
|
+
Leptris::XML::FFI.method(:leptris_c14n_canonicalize_ex), c_ptr,
|
|
410
410
|
version: version, mode: resolved_mode,
|
|
411
411
|
inclusive_namespaces: inclusive_namespaces,
|
|
412
412
|
with_comments: with_comments)
|
|
@@ -416,7 +416,7 @@ class Leptris::XML::Document
|
|
|
416
416
|
def free
|
|
417
417
|
return if @freed.state == :freed
|
|
418
418
|
@freed.state = :freed
|
|
419
|
-
Leptris::XML::FFI.leptris_document_free(
|
|
419
|
+
Leptris::XML::FFI.leptris_document_free(c_ptr) unless c_ptr.nil?
|
|
420
420
|
@c_ptr = nil
|
|
421
421
|
@c_address = nil
|
|
422
422
|
@wrapper_cache&.clear
|
|
@@ -432,7 +432,7 @@ class Leptris::XML::Document
|
|
|
432
432
|
# handlers. Returns self for chaining.
|
|
433
433
|
def exslt
|
|
434
434
|
Leptris::XML::FFI.check_status(
|
|
435
|
-
Leptris::XML::FFI.leptris_exslt_enable(
|
|
435
|
+
Leptris::XML::FFI.leptris_exslt_enable(c_ptr))
|
|
436
436
|
self
|
|
437
437
|
end
|
|
438
438
|
|
|
@@ -440,11 +440,11 @@ class Leptris::XML::Document
|
|
|
440
440
|
# an array of [target, data] pairs in document order.
|
|
441
441
|
def processing_instructions
|
|
442
442
|
return @processing_instructions if @pi_version == @version
|
|
443
|
-
count = Leptris::XML::FFI.leptris_document_pi_count(
|
|
443
|
+
count = Leptris::XML::FFI.leptris_document_pi_count(c_ptr)
|
|
444
444
|
result = count.times.map do |i|
|
|
445
|
-
[Leptris::XML::FFI.leptris_document_pi_target(
|
|
445
|
+
[Leptris::XML::FFI.leptris_document_pi_target(c_ptr, i),
|
|
446
446
|
Leptris::XML::FFI.read_pi_data(
|
|
447
|
-
Leptris::XML::FFI.leptris_document_pi_data(
|
|
447
|
+
Leptris::XML::FFI.leptris_document_pi_data(c_ptr, i)).to_s]
|
|
448
448
|
end
|
|
449
449
|
@processing_instructions = result
|
|
450
450
|
@pi_version = @version
|
|
@@ -460,10 +460,10 @@ class Leptris::XML::Document
|
|
|
460
460
|
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
461
461
|
if target_or_index.is_a?(Integer)
|
|
462
462
|
ptr = Leptris::XML::FFI.leptris_document_remove_pi(
|
|
463
|
-
|
|
463
|
+
c_ptr, nil, target_or_index)
|
|
464
464
|
else
|
|
465
465
|
ptr = Leptris::XML::FFI.leptris_document_remove_pi(
|
|
466
|
-
|
|
466
|
+
c_ptr, target_or_index.to_s, 0)
|
|
467
467
|
end
|
|
468
468
|
return nil if ptr.null?
|
|
469
469
|
@version += 1
|
|
@@ -473,7 +473,7 @@ class Leptris::XML::Document
|
|
|
473
473
|
# Append a document-level processing instruction. Returns self.
|
|
474
474
|
def add_pi(target, data = "")
|
|
475
475
|
witness = Leptris::XML::FFI.leptris_document_add_pi(
|
|
476
|
-
|
|
476
|
+
c_ptr, target.to_s, data.to_s)
|
|
477
477
|
raise Leptris::XML::Error, "leptris_document_add_pi failed" if witness.null?
|
|
478
478
|
@version += 1
|
|
479
479
|
self
|
|
@@ -485,7 +485,7 @@ class Leptris::XML::Document
|
|
|
485
485
|
# this is the writer). Returns self.
|
|
486
486
|
def add_comment(content)
|
|
487
487
|
witness = Leptris::XML::FFI.leptris_document_add_comment(
|
|
488
|
-
|
|
488
|
+
c_ptr, content.to_s)
|
|
489
489
|
raise Leptris::XML::Error,
|
|
490
490
|
"leptris_document_add_comment failed" if witness.null?
|
|
491
491
|
@version += 1
|
|
@@ -497,7 +497,7 @@ class Leptris::XML::Document
|
|
|
497
497
|
# (names, content, children, attributes) since they can never go
|
|
498
498
|
# stale. The C document is also frozen (advisory upstream). One-way.
|
|
499
499
|
def readonly!
|
|
500
|
-
Leptris::XML::FFI.leptris_document_freeze(
|
|
500
|
+
Leptris::XML::FFI.leptris_document_freeze(c_ptr)
|
|
501
501
|
@readonly = true
|
|
502
502
|
self
|
|
503
503
|
end
|
|
@@ -509,7 +509,7 @@ class Leptris::XML::Document
|
|
|
509
509
|
# True once #free has run (or the GC finalizer fired) — borrowed
|
|
510
510
|
# handles check this before dereferencing their c_ptr.
|
|
511
511
|
def freed?
|
|
512
|
-
@freed.state == :freed ||
|
|
512
|
+
@freed.state == :freed || c_ptr.nil?
|
|
513
513
|
end
|
|
514
514
|
|
|
515
515
|
# Document-level comments — parsed <!-- ... --> outside the
|
|
@@ -518,9 +518,9 @@ class Leptris::XML::Document
|
|
|
518
518
|
# 1.9.3, upstream #578). Version-memoized like the PI list.
|
|
519
519
|
def comments
|
|
520
520
|
return @comments if @comments_version == @version
|
|
521
|
-
count = Leptris::XML::FFI.leptris_document_comment_count(
|
|
521
|
+
count = Leptris::XML::FFI.leptris_document_comment_count(c_ptr)
|
|
522
522
|
result = Array.new(count) do |i|
|
|
523
|
-
Leptris::XML::FFI.leptris_document_comment_content(
|
|
523
|
+
Leptris::XML::FFI.leptris_document_comment_content(c_ptr, i)
|
|
524
524
|
end
|
|
525
525
|
@comments = result
|
|
526
526
|
@comments_version = @version
|
|
@@ -545,15 +545,15 @@ class Leptris::XML::Document
|
|
|
545
545
|
|
|
546
546
|
# The most recent error recorded against this document, or nil.
|
|
547
547
|
def last_error
|
|
548
|
-
msg = Leptris::XML::FFI.leptris_document_last_error(
|
|
548
|
+
msg = Leptris::XML::FFI.leptris_document_last_error(c_ptr)
|
|
549
549
|
msg.nil? || msg.empty? ? nil : msg
|
|
550
550
|
end
|
|
551
551
|
|
|
552
552
|
def name; "document"; end
|
|
553
553
|
def document; self; end
|
|
554
554
|
def encoding
|
|
555
|
-
return nil if
|
|
556
|
-
Leptris::XML::FFI.leptris_document_encoding(
|
|
555
|
+
return nil if c_ptr.nil?
|
|
556
|
+
Leptris::XML::FFI.leptris_document_encoding(c_ptr)
|
|
557
557
|
end
|
|
558
558
|
|
|
559
559
|
include Leptris::XML::Searchable
|
|
@@ -25,7 +25,7 @@ class Leptris::XML::DocumentFragment
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def children
|
|
28
|
-
pointers, kinds = Leptris::XML::FFI.fetch_children(
|
|
28
|
+
pointers, kinds = Leptris::XML::FFI.fetch_children(c_ptr)
|
|
29
29
|
nodes = Array.new(pointers.size) do |i|
|
|
30
30
|
Leptris::XML::Node.wrap(pointers[i], @document, node_type: kinds[i])
|
|
31
31
|
end
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -16,9 +16,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
16
16
|
return @name if @name
|
|
17
17
|
ensure_alive!
|
|
18
18
|
@name = if native_fast?
|
|
19
|
-
Leptris::XML::Native.fast_name(@
|
|
19
|
+
Leptris::XML::Native.fast_name(@c_address)
|
|
20
20
|
else
|
|
21
|
-
Leptris::XML::FFI.leptris_element_name(
|
|
21
|
+
Leptris::XML::FFI.leptris_element_name(c_ptr)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
alias_method :node_name, :name
|
|
@@ -26,7 +26,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
26
26
|
def name=(new_name)
|
|
27
27
|
ensure_writable!
|
|
28
28
|
Leptris::XML::FFI.check_status(
|
|
29
|
-
Leptris::XML::FFI.leptris_element_set_name(
|
|
29
|
+
Leptris::XML::FFI.leptris_element_set_name(c_ptr, new_name))
|
|
30
30
|
@name = new_name
|
|
31
31
|
end
|
|
32
32
|
alias_method :node_name=, :name=
|
|
@@ -35,9 +35,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
35
35
|
return @content if memo_hit?(@content_version)
|
|
36
36
|
ensure_alive!
|
|
37
37
|
result = if native_fast?
|
|
38
|
-
Leptris::XML::Native.fast_element_text(@
|
|
38
|
+
Leptris::XML::Native.fast_element_text(@c_address)
|
|
39
39
|
else
|
|
40
|
-
Leptris::XML::FFI.leptris_element_text(
|
|
40
|
+
Leptris::XML::FFI.leptris_element_text(c_ptr)
|
|
41
41
|
end
|
|
42
42
|
if @document
|
|
43
43
|
@content = result
|
|
@@ -49,7 +49,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
49
49
|
def content=(new_content)
|
|
50
50
|
ensure_writable!
|
|
51
51
|
Leptris::XML::FFI.check_status(
|
|
52
|
-
Leptris::XML::FFI.leptris_element_set_text(
|
|
52
|
+
Leptris::XML::FFI.leptris_element_set_text(c_ptr, new_content.to_s))
|
|
53
53
|
new_content
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -80,9 +80,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
80
80
|
return v if !v.nil? || @attributes
|
|
81
81
|
ensure_alive!
|
|
82
82
|
v = if native_fast?
|
|
83
|
-
Leptris::XML::Native.fast_attribute(@
|
|
83
|
+
Leptris::XML::Native.fast_attribute(@c_address, name)
|
|
84
84
|
else
|
|
85
|
-
Leptris::XML::FFI.leptris_element_attribute(
|
|
85
|
+
Leptris::XML::FFI.leptris_element_attribute(c_ptr, name)
|
|
86
86
|
end
|
|
87
87
|
values[name] = v
|
|
88
88
|
return v
|
|
@@ -93,9 +93,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
93
93
|
# stale @attributes hash and attributes would serve it.
|
|
94
94
|
ensure_alive!
|
|
95
95
|
v = if native_fast?
|
|
96
|
-
Leptris::XML::Native.fast_attribute(@
|
|
96
|
+
Leptris::XML::Native.fast_attribute(@c_address, name)
|
|
97
97
|
else
|
|
98
|
-
Leptris::XML::FFI.leptris_element_attribute(
|
|
98
|
+
Leptris::XML::FFI.leptris_element_attribute(c_ptr, name)
|
|
99
99
|
end
|
|
100
100
|
@attr_values = { name => v }
|
|
101
101
|
@attributes = nil
|
|
@@ -106,7 +106,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
106
106
|
return v
|
|
107
107
|
end
|
|
108
108
|
ensure_alive!
|
|
109
|
-
v = Leptris::XML::FFI.leptris_element_attribute(
|
|
109
|
+
v = Leptris::XML::FFI.leptris_element_attribute(c_ptr, name)
|
|
110
110
|
# Namespace-aware misses (an undeclared prefix never resolves
|
|
111
111
|
# through in-scope declarations) fall back to the WRITTEN name
|
|
112
112
|
# — the same flat face #attributes exposes, so a value written
|
|
@@ -124,23 +124,32 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
124
124
|
if native_fast_children?
|
|
125
125
|
Leptris::XML::FFI.check_status(
|
|
126
126
|
Leptris::XML::Native.set_binding_attribute(
|
|
127
|
-
@document, @
|
|
127
|
+
@document, @c_address, key.to_s, value.to_s))
|
|
128
128
|
return value
|
|
129
129
|
end
|
|
130
130
|
ensure_writable!
|
|
131
131
|
Leptris::XML::FFI.check_status(
|
|
132
|
-
Leptris::XML::FFI.leptris_element_set_attribute(
|
|
132
|
+
Leptris::XML::FFI.leptris_element_set_attribute(c_ptr, key.to_s, value.to_s))
|
|
133
133
|
value
|
|
134
134
|
end
|
|
135
135
|
alias_method :set_attribute, :[]=
|
|
136
136
|
|
|
137
137
|
def key?(name)
|
|
138
138
|
ensure_alive!
|
|
139
|
+
# Versioned attribute memo first (TODO.perf/21): a hit proves
|
|
140
|
+
# presence without the engine round-trip; the memo cannot
|
|
141
|
+
# prove absence for a partial fill, so misses fall through.
|
|
142
|
+
n = name.to_s
|
|
143
|
+
values = @attr_values
|
|
144
|
+
if @document && values && @attributes_version == @document.version &&
|
|
145
|
+
values.key?(n)
|
|
146
|
+
return true
|
|
147
|
+
end
|
|
139
148
|
return true if Leptris::XML::FFI.leptris_element_has_attribute(
|
|
140
|
-
|
|
149
|
+
c_ptr, n) != 0
|
|
141
150
|
# Same written-name fallback as #[] — the engine lookup is
|
|
142
151
|
# namespace-aware and misses undeclared prefixes (#161).
|
|
143
|
-
attributes.key?(
|
|
152
|
+
attributes.key?(n)
|
|
144
153
|
end
|
|
145
154
|
alias_method :has_attribute?, :key?
|
|
146
155
|
|
|
@@ -152,19 +161,19 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
152
161
|
def attribute_ns(uri, local)
|
|
153
162
|
ensure_alive!
|
|
154
163
|
Leptris::XML::FFI.leptris_element_attribute_ns(
|
|
155
|
-
|
|
164
|
+
c_ptr, uri&.to_s, local.to_s)
|
|
156
165
|
end
|
|
157
166
|
|
|
158
167
|
def has_attribute_ns?(uri, local)
|
|
159
168
|
ensure_alive!
|
|
160
169
|
Leptris::XML::FFI.leptris_element_has_attribute_ns(
|
|
161
|
-
|
|
170
|
+
c_ptr, uri&.to_s, local.to_s) != 0
|
|
162
171
|
end
|
|
163
172
|
|
|
164
173
|
def remove_attribute(name)
|
|
165
174
|
ensure_writable!
|
|
166
175
|
Leptris::XML::FFI.check_status(
|
|
167
|
-
Leptris::XML::FFI.leptris_element_remove_attribute(
|
|
176
|
+
Leptris::XML::FFI.leptris_element_remove_attribute(c_ptr, name.to_s))
|
|
168
177
|
self
|
|
169
178
|
end
|
|
170
179
|
alias_method :delete, :remove_attribute
|
|
@@ -177,10 +186,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
177
186
|
def each_attribute
|
|
178
187
|
return enum_for(:each_attribute) unless block_given?
|
|
179
188
|
ensure_alive!
|
|
180
|
-
attr = Leptris::XML::FFI.leptris_element_first_attribute(
|
|
189
|
+
attr = Leptris::XML::FFI.leptris_element_first_attribute(c_ptr)
|
|
181
190
|
until attr.nil? || attr.null?
|
|
182
191
|
name = Leptris::XML::FFI.leptris_attribute_get_name(attr)
|
|
183
|
-
value = Leptris::XML::FFI.leptris_attribute_get_value(
|
|
192
|
+
value = Leptris::XML::FFI.leptris_attribute_get_value(c_ptr, attr)
|
|
184
193
|
yield Leptris::XML::Attr.new(name, value, self, c_handle: attr)
|
|
185
194
|
attr = Leptris::XML::FFI.leptris_attribute_next(attr)
|
|
186
195
|
end
|
|
@@ -214,7 +223,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
214
223
|
if native_fast?
|
|
215
224
|
# TODO.perf/10: both memo faces in one C walk.
|
|
216
225
|
result, values = Leptris::XML::Native.bulk_attr_faces(
|
|
217
|
-
@
|
|
226
|
+
@c_address, self)
|
|
218
227
|
if @document
|
|
219
228
|
@attributes = result
|
|
220
229
|
@attr_values = values
|
|
@@ -251,9 +260,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
251
260
|
def prefix
|
|
252
261
|
ensure_alive!
|
|
253
262
|
if native_fast?
|
|
254
|
-
Leptris::XML::Native.fast_prefix(@
|
|
263
|
+
Leptris::XML::Native.fast_prefix(@c_address)
|
|
255
264
|
else
|
|
256
|
-
result = Leptris::XML::FFI.leptris_element_prefix(
|
|
265
|
+
result = Leptris::XML::FFI.leptris_element_prefix(c_ptr)
|
|
257
266
|
result if result && !result.empty?
|
|
258
267
|
end
|
|
259
268
|
end
|
|
@@ -324,7 +333,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
324
333
|
# the namespace lift — the full path below handles it.
|
|
325
334
|
if native_fast_children?
|
|
326
335
|
st = Leptris::XML::Native.insert_binding_child(
|
|
327
|
-
@document, @
|
|
336
|
+
@document, @c_address, node.c_ptr.address, 1)
|
|
328
337
|
unless st.nil?
|
|
329
338
|
Leptris::XML::FFI.check_status(st)
|
|
330
339
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -336,7 +345,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
336
345
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
337
346
|
end
|
|
338
347
|
Leptris::XML::FFI.check_status(
|
|
339
|
-
Leptris::XML::FFI.leptris_element_prepend_child(
|
|
348
|
+
Leptris::XML::FFI.leptris_element_prepend_child(c_ptr, node.c_ptr))
|
|
340
349
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
341
350
|
node
|
|
342
351
|
end
|
|
@@ -347,7 +356,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
347
356
|
# the namespace lift — the full path below handles it.
|
|
348
357
|
if native_fast_children?
|
|
349
358
|
st = Leptris::XML::Native.insert_binding_child(
|
|
350
|
-
@document, @
|
|
359
|
+
@document, @c_address, node.c_ptr.address, 2)
|
|
351
360
|
unless st.nil?
|
|
352
361
|
Leptris::XML::FFI.check_status(st)
|
|
353
362
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -359,7 +368,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
359
368
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
360
369
|
end
|
|
361
370
|
Leptris::XML::FFI.check_status(
|
|
362
|
-
Leptris::XML::FFI.leptris_element_insert_after(
|
|
371
|
+
Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
|
|
363
372
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
364
373
|
node
|
|
365
374
|
end
|
|
@@ -370,7 +379,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
370
379
|
# the namespace lift — the full path below handles it.
|
|
371
380
|
if native_fast_children?
|
|
372
381
|
st = Leptris::XML::Native.insert_binding_child(
|
|
373
|
-
@document, @
|
|
382
|
+
@document, @c_address, node.c_ptr.address, 3)
|
|
374
383
|
unless st.nil?
|
|
375
384
|
Leptris::XML::FFI.check_status(st)
|
|
376
385
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -382,7 +391,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
382
391
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
383
392
|
end
|
|
384
393
|
Leptris::XML::FFI.check_status(
|
|
385
|
-
Leptris::XML::FFI.leptris_element_insert_before(
|
|
394
|
+
Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
|
|
386
395
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
387
396
|
node
|
|
388
397
|
end
|
|
@@ -390,7 +399,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
390
399
|
def remove_child(node)
|
|
391
400
|
ensure_writable!
|
|
392
401
|
Leptris::XML::FFI.check_status(
|
|
393
|
-
Leptris::XML::FFI.leptris_element_remove_child(
|
|
402
|
+
Leptris::XML::FFI.leptris_element_remove_child(c_ptr, node.c_ptr))
|
|
394
403
|
node
|
|
395
404
|
end
|
|
396
405
|
|
|
@@ -398,7 +407,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
398
407
|
ensure_writable!
|
|
399
408
|
# Remove existing children, then attach the new ones in source order.
|
|
400
409
|
Leptris::XML::FFI.check_status(
|
|
401
|
-
Leptris::XML::FFI.leptris_element_remove_children(
|
|
410
|
+
Leptris::XML::FFI.leptris_element_remove_children(c_ptr))
|
|
402
411
|
Array(node_or_nodes).each { |n| add_child(n) }
|
|
403
412
|
end
|
|
404
413
|
|
|
@@ -460,7 +469,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
460
469
|
# document.create_element(name) + add_child.
|
|
461
470
|
def create_child(name)
|
|
462
471
|
ensure_writable!
|
|
463
|
-
ptr = Leptris::XML::FFI.leptris_element_create_child(
|
|
472
|
+
ptr = Leptris::XML::FFI.leptris_element_create_child(c_ptr, name.to_s)
|
|
464
473
|
raise Leptris::XML::Error,
|
|
465
474
|
"leptris_element_create_child failed for #{name.inspect}" if ptr.null?
|
|
466
475
|
Leptris::XML::Node.wrap_fresh(ptr, @document, Leptris::XML::FFI::NODE_ELEMENT)
|
|
@@ -477,7 +486,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
477
486
|
# to the full path.
|
|
478
487
|
if native_fast_children?
|
|
479
488
|
st = Leptris::XML::Native.append_binding_child(
|
|
480
|
-
@document, @
|
|
489
|
+
@document, @c_address, node_or_markup.c_ptr.address)
|
|
481
490
|
unless st.nil?
|
|
482
491
|
Leptris::XML::FFI.check_status(st)
|
|
483
492
|
Leptris::XML::Node.invalidate_cross_document!(node_or_markup, @document)
|
|
@@ -488,7 +497,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
488
497
|
Leptris::XML::Element.lift_namespaces_for_adoption(node_or_markup, namespaces)
|
|
489
498
|
end
|
|
490
499
|
Leptris::XML::FFI.check_status(
|
|
491
|
-
Leptris::XML::FFI.leptris_element_append_child(
|
|
500
|
+
Leptris::XML::FFI.leptris_element_append_child(c_ptr, node_or_markup.c_ptr))
|
|
492
501
|
Leptris::XML::Node.invalidate_cross_document!(node_or_markup, @document)
|
|
493
502
|
node_or_markup
|
|
494
503
|
when String
|
|
@@ -496,7 +505,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
496
505
|
added = []
|
|
497
506
|
frag.children.each do |n|
|
|
498
507
|
Leptris::XML::FFI.check_status(
|
|
499
|
-
Leptris::XML::FFI.leptris_element_append_child(
|
|
508
|
+
Leptris::XML::FFI.leptris_element_append_child(c_ptr, n.c_ptr))
|
|
500
509
|
added << n
|
|
501
510
|
end
|
|
502
511
|
Leptris::XML::NodeSet.new(@document, added)
|
|
@@ -509,7 +518,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
509
518
|
def namespace
|
|
510
519
|
return @namespace if memo_hit?(@namespace_version)
|
|
511
520
|
ensure_alive!
|
|
512
|
-
uri = Leptris::XML::FFI.leptris_element_namespace(
|
|
521
|
+
uri = Leptris::XML::FFI.leptris_element_namespace(c_ptr)
|
|
513
522
|
result =
|
|
514
523
|
if uri.nil? || uri.empty?
|
|
515
524
|
nil
|
|
@@ -517,7 +526,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
517
526
|
# The resolved namespace is reached via this element's own
|
|
518
527
|
# prefix, so carry it through: consumers that distinguish
|
|
519
528
|
# {"p" => "urn:p"} from the default {"nil => "urn:p"} need it.
|
|
520
|
-
prefix = Leptris::XML::FFI.leptris_element_prefix(
|
|
529
|
+
prefix = Leptris::XML::FFI.leptris_element_prefix(c_ptr)
|
|
521
530
|
prefix = nil if prefix.nil? || prefix.empty?
|
|
522
531
|
Leptris::XML::Namespace.new(self, uri, prefix: prefix)
|
|
523
532
|
end
|
|
@@ -538,7 +547,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
538
547
|
def namespace=(uri)
|
|
539
548
|
ensure_writable!
|
|
540
549
|
Leptris::XML::FFI.check_status(
|
|
541
|
-
Leptris::XML::FFI.leptris_element_set_namespace(
|
|
550
|
+
Leptris::XML::FFI.leptris_element_set_namespace(c_ptr, uri&.to_s))
|
|
542
551
|
uri
|
|
543
552
|
end
|
|
544
553
|
|
|
@@ -551,7 +560,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
551
560
|
prefix = ::FFI::MemoryPointer.new(:pointer)
|
|
552
561
|
uri = ::FFI::MemoryPointer.new(:pointer)
|
|
553
562
|
Leptris::XML::FFI.leptris_element_expanded_name(
|
|
554
|
-
|
|
563
|
+
c_ptr, local, prefix, uri)
|
|
555
564
|
local_ptr = local.read_pointer
|
|
556
565
|
prefix_ptr = prefix.read_pointer
|
|
557
566
|
uri_ptr = uri.read_pointer
|
|
@@ -569,10 +578,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
569
578
|
def namespace_definitions
|
|
570
579
|
return @namespace_definitions if memo_hit?(@namespace_definitions_version)
|
|
571
580
|
ensure_alive!
|
|
572
|
-
count = Leptris::XML::FFI.leptris_element_namespace_count(
|
|
581
|
+
count = Leptris::XML::FFI.leptris_element_namespace_count(c_ptr)
|
|
573
582
|
result = count.times.map do |i|
|
|
574
|
-
prefix = Leptris::XML::FFI.leptris_element_namespace_decl_prefix(
|
|
575
|
-
uri = Leptris::XML::FFI.leptris_element_namespace_decl_uri(
|
|
583
|
+
prefix = Leptris::XML::FFI.leptris_element_namespace_decl_prefix(c_ptr, i)
|
|
584
|
+
uri = Leptris::XML::FFI.leptris_element_namespace_decl_uri(c_ptr, i)
|
|
576
585
|
Leptris::XML::Namespace.new(self, uri, prefix: prefix)
|
|
577
586
|
end
|
|
578
587
|
if @document
|
|
@@ -612,7 +621,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
612
621
|
# One C pass (TODO.perf/18): the child chain, per-kind
|
|
613
622
|
# serialization, and the escape set all in one dispatch —
|
|
614
623
|
# byte-identical to the Ruby loop below.
|
|
615
|
-
return Leptris::XML::Native.fast_inner_xml(@
|
|
624
|
+
return Leptris::XML::Native.fast_inner_xml(@c_address) if
|
|
616
625
|
@native_fast
|
|
617
626
|
children.map do |child|
|
|
618
627
|
case child
|
|
@@ -646,9 +655,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
646
655
|
end
|
|
647
656
|
return Leptris::XML::Serialization.to_xml_element_unit(
|
|
648
657
|
self, indent_text, indent: indent) if indent_text.is_a?(String)
|
|
649
|
-
return Leptris::XML::Serialization.element_xml_expand_empty(
|
|
658
|
+
return Leptris::XML::Serialization.element_xml_expand_empty(c_ptr) if expand_empty
|
|
650
659
|
Leptris::XML::Serialization.to_xml(
|
|
651
|
-
Leptris::XML::Serialization::ELEMENT_SERIALIZE_INTO,
|
|
660
|
+
Leptris::XML::Serialization::ELEMENT_SERIALIZE_INTO, c_ptr,
|
|
652
661
|
indent: indent, no_decl: no_decl, encoding: encoding)
|
|
653
662
|
end
|
|
654
663
|
|
|
@@ -660,7 +669,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
660
669
|
resolved_mode = mode || (exclusive ? Leptris::XML::FFI::C14N_MODE_EXCLUSIVE
|
|
661
670
|
: Leptris::XML::FFI::C14N_MODE_CANONICAL)
|
|
662
671
|
Leptris::XML::Serialization.canonicalize(
|
|
663
|
-
Leptris::XML::FFI.method(:leptris_c14n_canonicalize_subtree_ex),
|
|
672
|
+
Leptris::XML::FFI.method(:leptris_c14n_canonicalize_subtree_ex), c_ptr,
|
|
664
673
|
version: version, mode: resolved_mode,
|
|
665
674
|
inclusive_namespaces: inclusive_namespaces,
|
|
666
675
|
with_comments: with_comments)
|
|
@@ -671,7 +680,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
671
680
|
ensure_writable!
|
|
672
681
|
Leptris::XML::FFI.check_status(
|
|
673
682
|
Leptris::XML::FFI.leptris_element_add_namespace_definition(
|
|
674
|
-
|
|
683
|
+
c_ptr, prefix.to_s, href.to_s))
|
|
675
684
|
Leptris::XML::Namespace.new(self, href.to_s, prefix: prefix.nil? ? nil : prefix.to_s)
|
|
676
685
|
end
|
|
677
686
|
alias_method :add_namespace, :add_namespace_definition
|
|
@@ -679,14 +688,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
679
688
|
def default_namespace=(href)
|
|
680
689
|
ensure_writable!
|
|
681
690
|
Leptris::XML::FFI.check_status(
|
|
682
|
-
Leptris::XML::FFI.leptris_element_set_default_namespace(
|
|
691
|
+
Leptris::XML::FFI.leptris_element_set_default_namespace(c_ptr, href.to_s))
|
|
683
692
|
href
|
|
684
693
|
end
|
|
685
694
|
|
|
686
695
|
def remove_namespace_definition(prefix)
|
|
687
696
|
ensure_writable!
|
|
688
697
|
Leptris::XML::FFI.check_status(
|
|
689
|
-
Leptris::XML::FFI.leptris_element_remove_namespace_definition(
|
|
698
|
+
Leptris::XML::FFI.leptris_element_remove_namespace_definition(c_ptr, prefix.to_s))
|
|
690
699
|
self
|
|
691
700
|
end
|
|
692
701
|
end
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Leptris::XML::Node
|
|
4
|
-
|
|
4
|
+
# TODO.perf/19: @c_address is the canonical truth; the Pointer
|
|
5
|
+
# materializes only when actually read (most wrappers' Pointers
|
|
6
|
+
# never are — hot reads are memoized or address-based).
|
|
7
|
+
def c_ptr
|
|
8
|
+
@c_ptr ||= ::FFI::Pointer.new(@c_address)
|
|
9
|
+
end
|
|
5
10
|
|
|
6
11
|
# Iterparse-yielded elements are owned by an IterationScope (the
|
|
7
12
|
# internal lifetime/memoization authority) — the public #document
|
|
@@ -12,6 +17,7 @@ class Leptris::XML::Node
|
|
|
12
17
|
|
|
13
18
|
def initialize(c_ptr, document, parent: nil, node_type: nil)
|
|
14
19
|
@c_ptr = c_ptr
|
|
20
|
+
@c_address = c_ptr&.address
|
|
15
21
|
@document = document
|
|
16
22
|
@parent = parent
|
|
17
23
|
# Structural-memo stamp (TODO.perf/13): a constructor-seeded
|
|
@@ -142,7 +148,7 @@ class Leptris::XML::Node
|
|
|
142
148
|
def type
|
|
143
149
|
return @node_type if @node_type
|
|
144
150
|
ensure_alive!
|
|
145
|
-
@node_type = Leptris::XML::FFI.leptris_node_get_type(
|
|
151
|
+
@node_type = Leptris::XML::FFI.leptris_node_get_type(c_ptr)
|
|
146
152
|
end
|
|
147
153
|
alias_method :node_type, :type
|
|
148
154
|
|
|
@@ -165,7 +171,7 @@ class Leptris::XML::Node
|
|
|
165
171
|
def digest(drop_ws: false)
|
|
166
172
|
ensure_alive!
|
|
167
173
|
Leptris::XML::FFI.leptris_node_digest(
|
|
168
|
-
|
|
174
|
+
c_ptr, drop_ws ? 1 : 0)
|
|
169
175
|
end
|
|
170
176
|
def text?; type == Leptris::XML::FFI::NODE_TEXT; end
|
|
171
177
|
def comment?; type == Leptris::XML::FFI::NODE_COMMENT; end
|
|
@@ -187,7 +193,7 @@ class Leptris::XML::Node
|
|
|
187
193
|
return @parent
|
|
188
194
|
end
|
|
189
195
|
ensure_alive!
|
|
190
|
-
ptr = Leptris::XML::FFI.leptris_node_parent(
|
|
196
|
+
ptr = Leptris::XML::FFI.leptris_node_parent(c_ptr)
|
|
191
197
|
result = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, @document)
|
|
192
198
|
if @document
|
|
193
199
|
@parent = result
|
|
@@ -205,7 +211,7 @@ class Leptris::XML::Node
|
|
|
205
211
|
# unstamped derivation at most once per version.
|
|
206
212
|
def unstamped_parent
|
|
207
213
|
ensure_alive!
|
|
208
|
-
ptr = Leptris::XML::FFI.leptris_node_parent(
|
|
214
|
+
ptr = Leptris::XML::FFI.leptris_node_parent(c_ptr)
|
|
209
215
|
ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, @document)
|
|
210
216
|
end
|
|
211
217
|
|
|
@@ -274,7 +280,7 @@ class Leptris::XML::Node
|
|
|
274
280
|
|
|
275
281
|
def line
|
|
276
282
|
ensure_alive!
|
|
277
|
-
Leptris::XML::FFI.leptris_node_line(
|
|
283
|
+
Leptris::XML::FFI.leptris_node_line(c_ptr)
|
|
278
284
|
end
|
|
279
285
|
|
|
280
286
|
# Byte offset of the node's markup in its parse source (the '<'
|
|
@@ -283,19 +289,19 @@ class Leptris::XML::Node
|
|
|
283
289
|
# documents >= 2 GiB.
|
|
284
290
|
def byte_offset
|
|
285
291
|
ensure_alive!
|
|
286
|
-
Leptris::XML::FFI.leptris_node_byte_offset(
|
|
292
|
+
Leptris::XML::FFI.leptris_node_byte_offset(c_ptr)
|
|
287
293
|
end
|
|
288
294
|
|
|
289
295
|
def <=>(other)
|
|
290
296
|
return nil unless other.is_a?(Leptris::XML::Node)
|
|
291
297
|
return nil unless @document == other.document
|
|
292
298
|
ensure_alive!
|
|
293
|
-
Leptris::XML::FFI.leptris_node_compare(
|
|
299
|
+
Leptris::XML::FFI.leptris_node_compare(c_ptr, other.c_ptr)
|
|
294
300
|
end
|
|
295
301
|
|
|
296
302
|
def child
|
|
297
303
|
ensure_alive!
|
|
298
|
-
ptr = Leptris::XML::FFI.leptris_node_first_child(
|
|
304
|
+
ptr = Leptris::XML::FFI.leptris_node_first_child(c_ptr)
|
|
299
305
|
return nil if ptr.null?
|
|
300
306
|
Leptris::XML::Node.wrap(ptr, @document, parent: as_element_or_self)
|
|
301
307
|
end
|
|
@@ -310,7 +316,7 @@ class Leptris::XML::Node
|
|
|
310
316
|
# TODO.perf/01 tail: one C pass constructs every binding
|
|
311
317
|
# wrapper (class dispatch + ivars + identity cache) — the
|
|
312
318
|
# per-child Ruby wrap frames disappear.
|
|
313
|
-
nodes = Leptris::XML::Native.bulk_children(@document, @
|
|
319
|
+
nodes = Leptris::XML::Native.bulk_children(@document, @c_address)
|
|
314
320
|
result = Leptris::XML::NodeSet.new(@document, nodes)
|
|
315
321
|
if @document
|
|
316
322
|
@children = result
|
|
@@ -319,7 +325,7 @@ class Leptris::XML::Node
|
|
|
319
325
|
return result
|
|
320
326
|
end
|
|
321
327
|
parent = as_element_or_self
|
|
322
|
-
pointers, kinds = Leptris::XML::FFI.fetch_children(
|
|
328
|
+
pointers, kinds = Leptris::XML::FFI.fetch_children(c_ptr)
|
|
323
329
|
nodes = Array.new(pointers.size) do |i|
|
|
324
330
|
Leptris::XML::Node.wrap(pointers[i], @document,
|
|
325
331
|
parent: parent, node_type: kinds[i])
|
|
@@ -337,7 +343,7 @@ class Leptris::XML::Node
|
|
|
337
343
|
return @next_sibling
|
|
338
344
|
end
|
|
339
345
|
ensure_alive!
|
|
340
|
-
ptr = Leptris::XML::FFI.leptris_node_next_sibling(
|
|
346
|
+
ptr = Leptris::XML::FFI.leptris_node_next_sibling(c_ptr)
|
|
341
347
|
result = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, @document, parent: @parent)
|
|
342
348
|
if @document
|
|
343
349
|
@next_sibling = result
|
|
@@ -352,7 +358,7 @@ class Leptris::XML::Node
|
|
|
352
358
|
return @previous_sibling
|
|
353
359
|
end
|
|
354
360
|
ensure_alive!
|
|
355
|
-
ptr = Leptris::XML::FFI.leptris_node_previous_sibling(
|
|
361
|
+
ptr = Leptris::XML::FFI.leptris_node_previous_sibling(c_ptr)
|
|
356
362
|
result = ptr.null? ? nil : Leptris::XML::Node.wrap(ptr, @document, parent: @parent)
|
|
357
363
|
if @document
|
|
358
364
|
@previous_sibling = result
|
|
@@ -368,7 +374,7 @@ class Leptris::XML::Node
|
|
|
368
374
|
# Raw pointer scan: non-element siblings are typed with one C
|
|
369
375
|
# call each — never wrapped, never cached — and the found
|
|
370
376
|
# element carries the ELEMENT hint into the wrap.
|
|
371
|
-
ptr = Leptris::XML::FFI.leptris_node_first_child(
|
|
377
|
+
ptr = Leptris::XML::FFI.leptris_node_first_child(c_ptr)
|
|
372
378
|
result = nil
|
|
373
379
|
until ptr.nil? || ptr.null?
|
|
374
380
|
if Leptris::XML::FFI.leptris_node_get_type(ptr) ==
|
|
@@ -391,7 +397,7 @@ class Leptris::XML::Node
|
|
|
391
397
|
# Element receivers: the element-only batch fetches pointers
|
|
392
398
|
# without wrapping any text child; only the last is wrapped.
|
|
393
399
|
if is_a?(Leptris::XML::Element)
|
|
394
|
-
kids = Leptris::XML::FFI.fetch_element_children(
|
|
400
|
+
kids = Leptris::XML::FFI.fetch_element_children(c_ptr)
|
|
395
401
|
return nil if kids.empty?
|
|
396
402
|
return Leptris::XML::Node.wrap(
|
|
397
403
|
kids.last, @document, parent: self,
|
|
@@ -408,10 +414,10 @@ class Leptris::XML::Node
|
|
|
408
414
|
# ELEMENT hint rides along). Other nodes keep the filter.
|
|
409
415
|
result =
|
|
410
416
|
if native_fast_children?
|
|
411
|
-
Leptris::XML::Native.bulk_element_children(@document, @
|
|
417
|
+
Leptris::XML::Native.bulk_element_children(@document, @c_address)
|
|
412
418
|
elsif is_a?(Leptris::XML::Element)
|
|
413
419
|
parent = as_element_or_self
|
|
414
|
-
Leptris::XML::FFI.fetch_element_children(
|
|
420
|
+
Leptris::XML::FFI.fetch_element_children(c_ptr).map do |ptr|
|
|
415
421
|
Leptris::XML::Node.wrap(ptr, @document, parent: parent,
|
|
416
422
|
node_type: Leptris::XML::FFI::NODE_ELEMENT)
|
|
417
423
|
end
|
|
@@ -441,7 +447,7 @@ class Leptris::XML::Node
|
|
|
441
447
|
def unlink
|
|
442
448
|
ensure_writable!
|
|
443
449
|
Leptris::XML::FFI.check_status(
|
|
444
|
-
Leptris::XML::FFI.leptris_node_unlink(
|
|
450
|
+
Leptris::XML::FFI.leptris_node_unlink(c_ptr))
|
|
445
451
|
@parent = nil
|
|
446
452
|
self
|
|
447
453
|
end
|
|
@@ -468,7 +474,7 @@ class Leptris::XML::Node
|
|
|
468
474
|
Leptris::XML::Node.wrap(node_ptr, document),
|
|
469
475
|
entering == 1, depth)
|
|
470
476
|
end
|
|
471
|
-
Leptris::XML::FFI.leptris_node_visit(
|
|
477
|
+
Leptris::XML::FFI.leptris_node_visit(c_ptr, visitor, nil)
|
|
472
478
|
self
|
|
473
479
|
end
|
|
474
480
|
|
|
@@ -497,7 +503,7 @@ class Leptris::XML::Node
|
|
|
497
503
|
return enum_for(:traverse) unless block_given?
|
|
498
504
|
ensure_alive!
|
|
499
505
|
error = nil
|
|
500
|
-
self_address = @
|
|
506
|
+
self_address = @c_address
|
|
501
507
|
callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
|
|
502
508
|
begin
|
|
503
509
|
yield Leptris::XML::Node.wrap(node_ptr, @document)
|
|
@@ -508,7 +514,7 @@ class Leptris::XML::Node
|
|
|
508
514
|
end
|
|
509
515
|
end
|
|
510
516
|
Leptris::XML::FFI.leptris_node_traverse(
|
|
511
|
-
|
|
517
|
+
c_ptr, Leptris::XML::FFI::TRAVERSE_POST_ORDER, callback, nil)
|
|
512
518
|
raise error if error
|
|
513
519
|
self
|
|
514
520
|
end
|
|
@@ -516,7 +522,7 @@ class Leptris::XML::Node
|
|
|
516
522
|
def path
|
|
517
523
|
return @path if memo_hit?(@path_version)
|
|
518
524
|
ensure_alive!
|
|
519
|
-
str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(
|
|
525
|
+
str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(c_ptr)
|
|
520
526
|
result = str_ptr.null? ? nil : Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
521
527
|
if @document
|
|
522
528
|
@path = result
|
|
@@ -555,7 +561,7 @@ class Leptris::XML::Node
|
|
|
555
561
|
|
|
556
562
|
def ==(other)
|
|
557
563
|
return false unless other.is_a?(Leptris::XML::Node)
|
|
558
|
-
|
|
564
|
+
c_ptr == other.c_ptr
|
|
559
565
|
end
|
|
560
566
|
|
|
561
567
|
def inspect
|
|
@@ -4,7 +4,7 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
4
4
|
def name
|
|
5
5
|
return @name if memo_hit?(@name_version)
|
|
6
6
|
ensure_alive!
|
|
7
|
-
result = Leptris::XML::FFI.leptris_pi_node_get_target(
|
|
7
|
+
result = Leptris::XML::FFI.leptris_pi_node_get_target(c_ptr)
|
|
8
8
|
if @document
|
|
9
9
|
@name = result
|
|
10
10
|
@name_version = @document.version
|
|
@@ -17,7 +17,7 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
17
17
|
return @content if memo_hit?(@content_version)
|
|
18
18
|
ensure_alive!
|
|
19
19
|
result = Leptris::XML::FFI.read_pi_data(
|
|
20
|
-
Leptris::XML::FFI.leptris_pi_node_get_data(
|
|
20
|
+
Leptris::XML::FFI.leptris_pi_node_get_data(c_ptr)).to_s
|
|
21
21
|
if @document
|
|
22
22
|
@content = result
|
|
23
23
|
@content_version = @document.version
|
|
@@ -31,14 +31,14 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
31
31
|
def target=(new_target)
|
|
32
32
|
ensure_writable!
|
|
33
33
|
Leptris::XML::FFI.check_status(
|
|
34
|
-
Leptris::XML::FFI.leptris_pi_node_set_target(
|
|
34
|
+
Leptris::XML::FFI.leptris_pi_node_set_target(c_ptr, new_target.to_s))
|
|
35
35
|
new_target
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def data=(new_data)
|
|
39
39
|
ensure_writable!
|
|
40
40
|
Leptris::XML::FFI.check_status(
|
|
41
|
-
Leptris::XML::FFI.leptris_pi_node_set_data(
|
|
41
|
+
Leptris::XML::FFI.leptris_pi_node_set_data(c_ptr, new_data.to_s))
|
|
42
42
|
new_data
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -48,7 +48,7 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
48
48
|
# comes out.
|
|
49
49
|
def unlink
|
|
50
50
|
ensure_writable!
|
|
51
|
-
rc = Leptris::XML::FFI.leptris_node_unlink(
|
|
51
|
+
rc = Leptris::XML::FFI.leptris_node_unlink(c_ptr)
|
|
52
52
|
if rc == Leptris::XML::FFI::LEPTRIS_ERROR_NOT_FOUND
|
|
53
53
|
index = document_pi_index
|
|
54
54
|
if index.negative?
|
|
@@ -56,7 +56,7 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
56
56
|
"PI not found in its document — already removed?"
|
|
57
57
|
end
|
|
58
58
|
removed = @document.remove_pi(index)
|
|
59
|
-
unless removed && removed.c_ptr ==
|
|
59
|
+
unless removed && removed.c_ptr == c_ptr
|
|
60
60
|
raise Leptris::XML::Error, "document-level PI removal missed"
|
|
61
61
|
end
|
|
62
62
|
@parent = nil
|
|
@@ -75,7 +75,7 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
75
75
|
pi_index = -1
|
|
76
76
|
@document.children.each do |child|
|
|
77
77
|
pi_index += 1 if child.pi?
|
|
78
|
-
return pi_index if child.c_ptr ==
|
|
78
|
+
return pi_index if child.c_ptr == c_ptr
|
|
79
79
|
end
|
|
80
80
|
-1
|
|
81
81
|
end
|
|
@@ -29,10 +29,31 @@ module Leptris::XML::Searchable
|
|
|
29
29
|
nil # fall back to the string path — its error surface is the contract
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# CSS translation cache (TODO.perf/20): the selector -> XPath
|
|
33
|
+
# translation is a pure function of (selector, prefix); without
|
|
34
|
+
# it every css call re-ran the regex machinery before the
|
|
35
|
+
# compiled-expression cache could hit. Same bounded-LRU shape.
|
|
36
|
+
def self.css_expression(selector, prefix)
|
|
37
|
+
cache = (@css_expressions ||= {})
|
|
38
|
+
key = "#{prefix}#{selector}"
|
|
39
|
+
if (hit = cache[key])
|
|
40
|
+
cache.delete(key)
|
|
41
|
+
cache[key] = hit # LRU refresh
|
|
42
|
+
return hit
|
|
43
|
+
end
|
|
44
|
+
expr = Leptris::XML::CssToXPath.convert(selector, prefix: prefix)
|
|
45
|
+
cache.shift while cache.size >= COMPILED_CACHE_LIMIT
|
|
46
|
+
cache[key] = expr
|
|
47
|
+
expr
|
|
48
|
+
end
|
|
49
|
+
|
|
32
50
|
def xpath(*paths)
|
|
33
51
|
handler, ns, version = parse_search_args(paths)
|
|
34
52
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
35
|
-
|
|
53
|
+
# Single-String shape (the common call): join would mint a
|
|
54
|
+
# fresh String per call even for one path.
|
|
55
|
+
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
56
|
+
: paths.join(" | ")
|
|
36
57
|
|
|
37
58
|
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
38
59
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
@@ -65,7 +86,10 @@ module Leptris::XML::Searchable
|
|
|
65
86
|
def at_xpath(*paths)
|
|
66
87
|
handler, ns, version = parse_search_args(paths)
|
|
67
88
|
raise ArgumentError, "custom XPath handlers not supported" if handler
|
|
68
|
-
|
|
89
|
+
# Single-String shape (the common call): join would mint a
|
|
90
|
+
# fresh String per call even for one path.
|
|
91
|
+
expr = paths.one? && paths.first.is_a?(String) ? paths.first
|
|
92
|
+
: paths.join(" | ")
|
|
69
93
|
|
|
70
94
|
doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
|
|
71
95
|
context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
|
|
@@ -121,7 +145,7 @@ module Leptris::XML::Searchable
|
|
|
121
145
|
# Nokogiri semantics: css is receiver-relative — absolute "//"
|
|
122
146
|
# from a Document, descendant ".//" from elements and fragments.
|
|
123
147
|
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
124
|
-
expr = args.map { |r| Leptris::XML::
|
|
148
|
+
expr = args.map { |r| Leptris::XML::Searchable.css_expression(r, prefix) }
|
|
125
149
|
.join(" | ")
|
|
126
150
|
xpath(expr)
|
|
127
151
|
end
|
|
@@ -132,7 +156,7 @@ module Leptris::XML::Searchable
|
|
|
132
156
|
raise ArgumentError, "version: is XPath-only" if version
|
|
133
157
|
raise ArgumentError, "custom CSS handlers not supported" if handler
|
|
134
158
|
prefix = is_a?(Leptris::XML::Document) ? "//" : ".//"
|
|
135
|
-
expr = args.map { |r| Leptris::XML::
|
|
159
|
+
expr = args.map { |r| Leptris::XML::Searchable.css_expression(r, prefix) }
|
|
136
160
|
.join(" | ")
|
|
137
161
|
at_xpath(expr)
|
|
138
162
|
end
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -6,7 +6,7 @@ class Leptris::XML::Text < 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_text_node_get_content(
|
|
9
|
+
result = Leptris::XML::FFI.leptris_text_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::Text < 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_text_node_set_content(
|
|
20
|
+
Leptris::XML::FFI.leptris_text_node_set_content(c_ptr, new_content.to_s))
|
|
21
21
|
new_content
|
|
22
22
|
end
|
|
23
23
|
end
|
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.174.
|
|
4
|
+
version: 1.9.174.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -87,6 +87,9 @@ files:
|
|
|
87
87
|
- TODO.perf/16-at-xpath-single-result-seam.md
|
|
88
88
|
- TODO.perf/17-precomputed-fast-path-flags.md
|
|
89
89
|
- TODO.perf/18-inner-html-one-c-pass.md
|
|
90
|
+
- TODO.perf/19-lazy-node-pointer.md
|
|
91
|
+
- TODO.perf/20-css-translation-cache.md
|
|
92
|
+
- TODO.perf/21-key-memo-consult.md
|
|
90
93
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
91
94
|
- TODO.restructure/02-deep-copy-seam.md
|
|
92
95
|
- TODO.restructure/03-evaluation-context-seam.md
|