leptris 1.9.174.6 → 1.9.174.7
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 +15 -0
- data/TODO.perf/29-ns-xpath-compiled.md +23 -0
- data/TODO.perf/30-copy-and-element-child-faces.md +31 -0
- data/ext/leptris/native/native.c +120 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +13 -1
- data/lib/leptris/xml/node.rb +34 -2
- data/lib/leptris/xml/searchable.rb +20 -2
- data/lib/leptris/xml/xpath.rb +8 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b55134beeb4963b45dab45f2702b840c8db5e4f1c5e282622f5e4c4b5f55b1e
|
|
4
|
+
data.tar.gz: 92278400c3c9a6ff0450a151583a9edd383677b621d6c95bdbd5286d0ba0b8a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e476a9c2aaecd83069e118a4a7c1aa4ed141df2c853b238f4eb745b916be151ce46b0a5c29686f3d481d2bb816fe4c6f4744df7e76835bfe53ccc82c998134d
|
|
7
|
+
data.tar.gz: af8d33a0aaa87d762405979bcbc08be5af416e8dac2de924027eb900184df6740fcc4a8ef30e533becf131db08c2502a9dfc4a634b9678d9a87b5fa0513af212
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.7] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Namespace-bound xpath on the compiled path (TODO.perf/29)**:
|
|
13
|
+
the ns branch rides the compiled-expression cache (the handle
|
|
14
|
+
is ns-independent) plus the cached ns set through
|
|
15
|
+
`XPath#eval_ns_ptrs`, then the eager materializer — measured
|
|
16
|
+
(load ~16): repeat ns-xpath 7.9µs → 3.1µs (2.5x).
|
|
17
|
+
- **dup + element-child faces (TODO.perf/30)**: `Element#dup`
|
|
18
|
+
runs engine create + handle + element_copy + rooted wrap in
|
|
19
|
+
one C dispatch (the namespace-lift decision stays in Ruby) —
|
|
20
|
+
ns-bearing dup 37.0µs → 9.6µs (3.9x); `first/last_element_child`
|
|
21
|
+
answer in one C walk each (the FFI paths paid O(N) scans).
|
|
22
|
+
|
|
8
23
|
## [1.9.174.6] - 2026-09-15
|
|
9
24
|
|
|
10
25
|
### Changed
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 29 — Namespace-bound xpath on the compiled + eager path
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.7)
|
|
4
|
+
|
|
5
|
+
TODO.perf/15 excluded namespace-bound evaluations from the
|
|
6
|
+
compiled-expression cache — they still pay the engine's string
|
|
7
|
+
parse per call (~2-3µs) on top of an already-cached ns set. The
|
|
8
|
+
engine has leptris_xpath_compiled_eval_ns; route the ns branch
|
|
9
|
+
of xpath/at_xpath through the compiled cache (the handle is
|
|
10
|
+
ns-independent — compile once) and the cached ns set, then let
|
|
11
|
+
the eager materializer (TODO.perf/22) take the result.
|
|
12
|
+
|
|
13
|
+
Gates: ns-bound results identical (prefix resolution, missing
|
|
14
|
+
prefixes raising as today); the version-pinned path untouched;
|
|
15
|
+
suite both modes.
|
|
16
|
+
|
|
17
|
+
## Outcome (1.9.174.7)
|
|
18
|
+
|
|
19
|
+
The ns branch of xpath/at_xpath rides the compiled-expression
|
|
20
|
+
cache (the handle is ns-independent) + the cached ns set via
|
|
21
|
+
XPath#eval_ns_ptrs (leptris_xpath_compiled_eval_ns); results
|
|
22
|
+
flow into the eager materializer. Measured (load ~16): repeat
|
|
23
|
+
ns-xpath 7.9µs -> 3.1µs (2.5x).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 30 — dup one-dispatch + first/last element child faces
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.7)
|
|
4
|
+
|
|
5
|
+
Node#dup runs create + FFI element_copy + root= (three dispatches
|
|
6
|
+
plus the lift-guard walk) for an engine-copier op that is already
|
|
7
|
+
2.3x — bind the whole copy: engine create + handle + element_copy
|
|
8
|
+
+ root wrap in one face. And the element-child readers pay O(N):
|
|
9
|
+
first_element_child scans siblings with two FFI calls per
|
|
10
|
+
non-element (text-heavy parents), last_element_children batches
|
|
11
|
+
ALL children to keep one — one C walk each answers in a single
|
|
12
|
+
dispatch.
|
|
13
|
+
|
|
14
|
+
Gates: dup semantics (comment/PI children, namespaces — the
|
|
15
|
+
copy_of seam contract #696/#721/#812); first/last on mixed
|
|
16
|
+
content, empty elements, document receivers; suite both modes.
|
|
17
|
+
|
|
18
|
+
## Outcome (1.9.174.7)
|
|
19
|
+
|
|
20
|
+
Native.copy_binding_element: engine create + lifetime handle +
|
|
21
|
+
document wrapper + element_copy + rooted+memo-seeded wrap in one
|
|
22
|
+
dispatch; the namespace-lift decision stays in Ruby (skip_
|
|
23
|
+
adoption_lift? + lift — the copy_of seam's #696/#721/#812
|
|
24
|
+
semantics), and the return shape matches copy_of's contract (the
|
|
25
|
+
copied root ELEMENT — my first version returned the document and
|
|
26
|
+
the probe caught it). Native.first/last_element_child: one C
|
|
27
|
+
walk each (the FFI scan paid two calls per skipped sibling; the
|
|
28
|
+
batch fetch materialized every child to keep one). Measured
|
|
29
|
+
(load ~16): ns-bearing element dup 37.0µs -> 9.6µs (3.9x);
|
|
30
|
+
first_element_child over 1000 mixed children 98ns (memo-hit
|
|
31
|
+
shape), last 3.2µs cold-walk.
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -93,12 +93,14 @@ typedef void (*visit_cb_fn)(void *, void *, int, int);
|
|
|
93
93
|
typedef void (*visit_fn)(void *, visit_cb_fn, void *);
|
|
94
94
|
typedef void (*free_str_fn)(void *);
|
|
95
95
|
typedef const char *(*node_xpath_fn)(void *);
|
|
96
|
+
typedef void *(*elem_copy_fn)(void *, void *);
|
|
96
97
|
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
97
98
|
static node_unlink_fn f_node_unlink;
|
|
98
99
|
static traverse_fn f_node_traverse;
|
|
99
100
|
static visit_fn f_node_visit;
|
|
100
101
|
static free_str_fn f_free_str;
|
|
101
102
|
static node_xpath_fn f_node_xpath;
|
|
103
|
+
static elem_copy_fn f_elem_copy;
|
|
102
104
|
static set_root_fn f_set_root;
|
|
103
105
|
static doc_free_fn f_doc_free;
|
|
104
106
|
|
|
@@ -117,6 +119,8 @@ static void resolve_binding_classes(void);
|
|
|
117
119
|
static VALUE binding_cache_of(VALUE document);
|
|
118
120
|
static VALUE binding_klass_for(int kind);
|
|
119
121
|
static VALUE c_iteration_scope;
|
|
122
|
+
static VALUE c_b_document, c_b_freed, c_ffi_pointer;
|
|
123
|
+
static ID id_ptr_new, id_freed_new, id_alive;
|
|
120
124
|
|
|
121
125
|
#define NT_ELEMENT 0
|
|
122
126
|
|
|
@@ -216,6 +220,7 @@ static void resolve_symbols(const char *lib_path)
|
|
|
216
220
|
f_node_visit = (visit_fn)lib_sym(h, "leptris_node_visit");
|
|
217
221
|
f_free_str = (free_str_fn)lib_sym(h, "leptris_free_string");
|
|
218
222
|
f_node_xpath = (node_xpath_fn)lib_sym(h, "leptris_node_get_xpath");
|
|
223
|
+
f_elem_copy = (elem_copy_fn)lib_sym(h, "leptris_element_copy");
|
|
219
224
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
220
225
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
221
226
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -228,7 +233,7 @@ static void resolve_symbols(const char *lib_path)
|
|
|
228
233
|
!f_pi_data || !f_elem_set_name || !f_elem_set_text ||
|
|
229
234
|
!f_text_set_content || !f_node_unlink ||
|
|
230
235
|
!f_node_traverse || !f_node_visit || !f_free_str ||
|
|
231
|
-
!f_node_xpath ||
|
|
236
|
+
!f_node_xpath || !f_elem_copy ||
|
|
232
237
|
!f_doc_free ||
|
|
233
238
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
234
239
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -915,6 +920,72 @@ static VALUE nf_fast_pi_data(VALUE self, VALUE addr)
|
|
|
915
920
|
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
916
921
|
}
|
|
917
922
|
|
|
923
|
+
/* Shared construction for address-walk faces (TODO.perf/30):
|
|
924
|
+
* cache lookup + kind-dispatched binding wrapper. */
|
|
925
|
+
static VALUE wrap_addr_node(VALUE document, void *ptr)
|
|
926
|
+
{
|
|
927
|
+
VALUE cache, key, node;
|
|
928
|
+
int kind;
|
|
929
|
+
|
|
930
|
+
kind = f_node_type(ptr);
|
|
931
|
+
cache = binding_cache_of(document);
|
|
932
|
+
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
933
|
+
node = rb_hash_aref(cache, key);
|
|
934
|
+
if (NIL_P(node)) {
|
|
935
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
936
|
+
rb_iv_set(node, "@c_address", key);
|
|
937
|
+
rb_iv_set(node, "@document", document);
|
|
938
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
939
|
+
if (rb_obj_class(document) == c_iteration_scope) {
|
|
940
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
941
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
942
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
943
|
+
} else {
|
|
944
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
945
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
946
|
+
rb_iv_set(node, "@pub_document", document);
|
|
947
|
+
}
|
|
948
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
949
|
+
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
950
|
+
rb_hash_aset(cache, key, node);
|
|
951
|
+
}
|
|
952
|
+
return node;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* First element child in one C walk (text-heavy parents paid two
|
|
956
|
+
* FFI calls per skipped sibling). */
|
|
957
|
+
static VALUE nf_first_element_child(VALUE self, VALUE document,
|
|
958
|
+
VALUE addr)
|
|
959
|
+
{
|
|
960
|
+
void *p;
|
|
961
|
+
|
|
962
|
+
(void)self;
|
|
963
|
+
resolve_binding_classes();
|
|
964
|
+
for (p = f_first_child((void *)(uintptr_t)NUM2ULL(addr));
|
|
965
|
+
p != NULL; p = f_next_sibling(p)) {
|
|
966
|
+
if (f_node_type(p) == NT_ELEMENT)
|
|
967
|
+
return wrap_addr_node(document, p);
|
|
968
|
+
}
|
|
969
|
+
return Qnil;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/* Last element child in one C walk (the batch fetch materialized
|
|
973
|
+
* every child to keep one). */
|
|
974
|
+
static VALUE nf_last_element_child(VALUE self, VALUE document,
|
|
975
|
+
VALUE addr)
|
|
976
|
+
{
|
|
977
|
+
void *p, *last = NULL;
|
|
978
|
+
|
|
979
|
+
(void)self;
|
|
980
|
+
resolve_binding_classes();
|
|
981
|
+
for (p = f_first_child((void *)(uintptr_t)NUM2ULL(addr));
|
|
982
|
+
p != NULL; p = f_next_sibling(p)) {
|
|
983
|
+
if (f_node_type(p) == NT_ELEMENT)
|
|
984
|
+
last = p;
|
|
985
|
+
}
|
|
986
|
+
return last ? wrap_addr_node(document, last) : Qnil;
|
|
987
|
+
}
|
|
988
|
+
|
|
918
989
|
/* Owned string: copy + free through the engine's seam (the
|
|
919
990
|
* read_owned_string protocol). */
|
|
920
991
|
static VALUE nf_fast_path(VALUE self, VALUE addr)
|
|
@@ -1737,6 +1808,48 @@ static const rb_data_type_t dh_type = {
|
|
|
1737
1808
|
static VALUE c_doc_handle;
|
|
1738
1809
|
static ID id_iv_doc_handle, id_freed_new, id_alive;
|
|
1739
1810
|
|
|
1811
|
+
/* Node#dup in one dispatch (TODO.perf/30): engine create +
|
|
1812
|
+
* lifetime handle + document wrapper + element_copy + the copy
|
|
1813
|
+
* wrapped-and-rooted (memo-seeded). The namespace lift stays a
|
|
1814
|
+
* Ruby decision (skip_adoption_lift? — the copy_of seam's
|
|
1815
|
+
* semantics, #696/#721/#812). */
|
|
1816
|
+
static VALUE nf_copy_binding_element(VALUE self, VALUE document,
|
|
1817
|
+
VALUE addr)
|
|
1818
|
+
{
|
|
1819
|
+
void *doc, *copy;
|
|
1820
|
+
VALUE new_doc, doc_addr, handle, freed, root;
|
|
1821
|
+
struct doc_handle *h;
|
|
1822
|
+
|
|
1823
|
+
(void)self;
|
|
1824
|
+
resolve_binding_classes();
|
|
1825
|
+
doc = f_doc_create();
|
|
1826
|
+
if (!doc)
|
|
1827
|
+
return Qnil;
|
|
1828
|
+
doc_addr = ULL2NUM((uint64_t)(uintptr_t)doc);
|
|
1829
|
+
new_doc = rb_obj_alloc(c_b_document);
|
|
1830
|
+
rb_iv_set(new_doc, "@c_ptr",
|
|
1831
|
+
rb_funcall(c_ffi_pointer, id_ptr_new, 1, doc_addr));
|
|
1832
|
+
rb_iv_set(new_doc, "@c_address", doc_addr);
|
|
1833
|
+
freed = rb_funcall(c_b_freed, id_freed_new, 1, ID2SYM(id_alive));
|
|
1834
|
+
rb_iv_set(new_doc, "@freed", freed);
|
|
1835
|
+
rb_iv_set(new_doc, "@readonly", Qfalse);
|
|
1836
|
+
rb_iv_set(new_doc, "@version", INT2FIX(0));
|
|
1837
|
+
handle = TypedData_Make_Struct(c_doc_handle, struct doc_handle,
|
|
1838
|
+
&dh_type, h);
|
|
1839
|
+
h->doc = doc;
|
|
1840
|
+
rb_ivar_set(new_doc, id_iv_doc_handle, handle);
|
|
1841
|
+
|
|
1842
|
+
copy = f_elem_copy((void *)(uintptr_t)NUM2ULL(addr), doc);
|
|
1843
|
+
if (!copy) {
|
|
1844
|
+
h->doc = NULL; /* the handle releases the empty doc */
|
|
1845
|
+
return Qnil;
|
|
1846
|
+
}
|
|
1847
|
+
root = wrap_addr_node(new_doc, copy);
|
|
1848
|
+
rb_ivar_set(new_doc, rb_intern("@root"), root);
|
|
1849
|
+
rb_ivar_set(new_doc, rb_intern("@root_version"), INT2FIX(0));
|
|
1850
|
+
return new_doc;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1740
1853
|
static VALUE nf_doc_handle_attach(VALUE self, VALUE document)
|
|
1741
1854
|
{
|
|
1742
1855
|
struct doc_handle *h;
|
|
@@ -1952,6 +2065,12 @@ void Init_native(void)
|
|
|
1952
2065
|
nf_fast_pi_data, 1);
|
|
1953
2066
|
rb_define_module_function(m_native, "fast_path",
|
|
1954
2067
|
nf_fast_path, 1);
|
|
2068
|
+
rb_define_module_function(m_native, "first_element_child",
|
|
2069
|
+
nf_first_element_child, 2);
|
|
2070
|
+
rb_define_module_function(m_native, "last_element_child",
|
|
2071
|
+
nf_last_element_child, 2);
|
|
2072
|
+
rb_define_module_function(m_native, "copy_binding_element",
|
|
2073
|
+
nf_copy_binding_element, 2);
|
|
1955
2074
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
1956
2075
|
nf_fast_inner_xml, 1);
|
|
1957
2076
|
rb_define_module_function(m_native, "doc_handle_attach",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -497,9 +497,21 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
497
497
|
|
|
498
498
|
# Deep copy in a NEW document via Document.copy_of (the single
|
|
499
499
|
# copy seam — every child kind and namespace survives,
|
|
500
|
-
# #696/#721/#812).
|
|
500
|
+
# #696/#721/#812). TODO.perf/30: one C dispatch when the ext
|
|
501
|
+
# is loaded — the namespace-lift decision stays here.
|
|
501
502
|
def dup
|
|
502
503
|
ensure_alive!
|
|
504
|
+
if @addr_reads_fast
|
|
505
|
+
doc = Leptris::XML::Native.copy_binding_element(
|
|
506
|
+
@document, @c_address)
|
|
507
|
+
unless doc.nil?
|
|
508
|
+
copied = doc.root
|
|
509
|
+
unless Leptris::XML::Element.skip_adoption_lift?(copied)
|
|
510
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(copied, {})
|
|
511
|
+
end
|
|
512
|
+
return copied
|
|
513
|
+
end
|
|
514
|
+
end
|
|
503
515
|
Leptris::XML::Document.copy_of(self)
|
|
504
516
|
end
|
|
505
517
|
alias_method :clone, :dup
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -390,6 +390,17 @@ class Leptris::XML::Node
|
|
|
390
390
|
def first_element_child
|
|
391
391
|
return @first_element_child if memo_hit?(@first_element_child_version)
|
|
392
392
|
ensure_alive!
|
|
393
|
+
# TODO.perf/30: one C walk (the FFI scan paid two calls per
|
|
394
|
+
# skipped non-element sibling).
|
|
395
|
+
if @addr_reads_fast
|
|
396
|
+
result = Leptris::XML::Native.first_element_child(
|
|
397
|
+
@document, @c_address)
|
|
398
|
+
if @document
|
|
399
|
+
@first_element_child = result
|
|
400
|
+
@first_element_child_version = @document.version
|
|
401
|
+
end
|
|
402
|
+
return result
|
|
403
|
+
end
|
|
393
404
|
# Raw pointer scan: non-element siblings are typed with one C
|
|
394
405
|
# call each — never wrapped, never cached — and the found
|
|
395
406
|
# element carries the ELEMENT hint into the wrap.
|
|
@@ -413,8 +424,12 @@ class Leptris::XML::Node
|
|
|
413
424
|
end
|
|
414
425
|
|
|
415
426
|
def last_element_child
|
|
416
|
-
#
|
|
417
|
-
#
|
|
427
|
+
# TODO.perf/30: one C walk — the batch fetch materialized
|
|
428
|
+
# every child pointer to keep one.
|
|
429
|
+
if @addr_reads_fast
|
|
430
|
+
return Leptris::XML::Native.last_element_child(
|
|
431
|
+
@document, @c_address)
|
|
432
|
+
end
|
|
418
433
|
if is_a?(Leptris::XML::Element)
|
|
419
434
|
kids = Leptris::XML::FFI.fetch_element_children(c_ptr)
|
|
420
435
|
return nil if kids.empty?
|
|
@@ -600,6 +615,23 @@ class Leptris::XML::Node
|
|
|
600
615
|
# non-element kinds rebuild by value — #161).
|
|
601
616
|
def dup
|
|
602
617
|
ensure_alive!
|
|
618
|
+
# TODO.perf/30: elements copy in one C dispatch (engine
|
|
619
|
+
# create + handle + element_copy + rooted wrap). The
|
|
620
|
+
# namespace lift stays a Ruby decision — the copy_of seam's
|
|
621
|
+
# semantics (#696/#721/#812) run below when needed.
|
|
622
|
+
if element? && @addr_reads_fast
|
|
623
|
+
doc = Leptris::XML::Native.copy_binding_element(
|
|
624
|
+
@document, @c_address)
|
|
625
|
+
unless doc.nil?
|
|
626
|
+
# copy_of's contract returns the copied ROOT (the last
|
|
627
|
+
# expression of the root= assignment), not the document.
|
|
628
|
+
copied = doc.root
|
|
629
|
+
unless Leptris::XML::Element.skip_adoption_lift?(copied)
|
|
630
|
+
Leptris::XML::Element.lift_namespaces_for_adoption(copied, {})
|
|
631
|
+
end
|
|
632
|
+
return copied
|
|
633
|
+
end
|
|
634
|
+
end
|
|
603
635
|
Leptris::XML::Document.copy_of(self)
|
|
604
636
|
end
|
|
605
637
|
alias_method :clone, :dup
|
|
@@ -69,7 +69,16 @@ module Leptris::XML::Searchable
|
|
|
69
69
|
doc_ptr, context_ptr, expr,
|
|
70
70
|
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
71
71
|
elsif ns && !ns.empty?
|
|
72
|
-
|
|
72
|
+
# TODO.perf/29: the compiled handle is ns-independent —
|
|
73
|
+
# ride the cache and the cached ns set instead of the
|
|
74
|
+
# engine's string parse per call.
|
|
75
|
+
if (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
76
|
+
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
77
|
+
compiled.eval_ns_ptrs(doc_ptr, context_ptr, set)
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
81
|
+
end
|
|
73
82
|
elsif (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
74
83
|
compiled.eval_ptrs(doc_ptr, context_ptr)
|
|
75
84
|
else
|
|
@@ -105,7 +114,16 @@ module Leptris::XML::Searchable
|
|
|
105
114
|
doc_ptr, context_ptr, expr,
|
|
106
115
|
Leptris::XML::Searchable.xpath_version_code(version), nil)
|
|
107
116
|
elsif ns && !ns.empty?
|
|
108
|
-
|
|
117
|
+
# TODO.perf/29: the compiled handle is ns-independent —
|
|
118
|
+
# ride the cache and the cached ns set instead of the
|
|
119
|
+
# engine's string parse per call.
|
|
120
|
+
if (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
121
|
+
Leptris::XML::FFI.with_ns_set(ns) do |set|
|
|
122
|
+
compiled.eval_ns_ptrs(doc_ptr, context_ptr, set)
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
xpath_eval_with_namespaces(doc_ptr, context_ptr, expr, ns)
|
|
126
|
+
end
|
|
109
127
|
elsif (compiled = Leptris::XML::Searchable.compiled_expression(expr))
|
|
110
128
|
compiled.eval_ptrs(doc_ptr, context_ptr)
|
|
111
129
|
else
|
data/lib/leptris/xml/xpath.rb
CHANGED
|
@@ -40,6 +40,14 @@ class Leptris::XML::XPath
|
|
|
40
40
|
@handle, doc_ptr, context_ptr)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Namespace-bound evaluation against the compiled handle
|
|
44
|
+
# (TODO.perf/29): the handle is ns-independent — compile once,
|
|
45
|
+
# bind per call through the cached ns set.
|
|
46
|
+
def eval_ns_ptrs(doc_ptr, context_ptr, ns_set_ptr)
|
|
47
|
+
Leptris::XML::FFI.leptris_xpath_compiled_eval_ns(
|
|
48
|
+
@handle, doc_ptr, context_ptr, ns_set_ptr)
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
def initialize(expression, handle, version_code = nil)
|
|
44
52
|
@expression = expression
|
|
45
53
|
@handle = handle
|
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.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -97,6 +97,8 @@ files:
|
|
|
97
97
|
- TODO.perf/26-post-mutation-memo-seeding.md
|
|
98
98
|
- TODO.perf/27-c-yield-traversal.md
|
|
99
99
|
- TODO.perf/28-address-fills.md
|
|
100
|
+
- TODO.perf/29-ns-xpath-compiled.md
|
|
101
|
+
- TODO.perf/30-copy-and-element-child-faces.md
|
|
100
102
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
101
103
|
- TODO.restructure/02-deep-copy-seam.md
|
|
102
104
|
- TODO.restructure/03-evaluation-context-seam.md
|