leptris 1.9.232.1-x86_64-linux → 1.9.237.0-x86_64-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 +25 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +144 -3
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +20 -3
- data/lib/leptris/xml/ffi.rb +7 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +113 -4
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/benchmarks/dom/bench_leptris.c +14 -6
- data/vendor-src/libleptris/src/include/leptris.h +27 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +20 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +5 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +7 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +38 -4
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +5 -1
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +86 -7
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +237 -24
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +16 -1
- data/vendor-src/libleptris/test/dom/test_dom.cpp +80 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/count.xml +3117 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/empty.xml +502 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/exists.xml +537 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/head.xml +90 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/tail.xml +68 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +124 -93
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +28 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0980ce39059f99e83b70c3ed61b4321646a504f9b8215ecd20ecc6e74186bf2
|
|
4
|
+
data.tar.gz: 90bae571c397117bf80df020632b011a992e4237b496a4239648126a70c6a18a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1992c190b97c21cc24b0bfd3a881bf8ed4ae989e97e80ba6addb281c8834fc93ba7ea2b03cb9b00baddd1ac62c223720f7189190d3f74b44a4b2dca817db3672
|
|
7
|
+
data.tar.gz: bb5fd9e39cb45c2aad5b363ace91319334d39c341eb4bc2894484b96db56eb8c05c4eb10b8fa310bbdfdad409bd5ebabb60d2364694776003b9db77507c59d0e
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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.237.0] - 2026-09-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `Document#create_element` accepts an attributes hash:
|
|
13
|
+
`doc.create_element("book", "id" => "1", "lang" => "en")` creates
|
|
14
|
+
the element with every attribute in ONE engine crossing
|
|
15
|
+
(leptris_element_new_with_attributes, #1344) — builder paths
|
|
16
|
+
collapse from 1 + N crossings to one. Duplicate names replace,
|
|
17
|
+
last wins; `nil` values store the empty string (engine
|
|
18
|
+
semantics). The native face falls back to create + per-pair sets
|
|
19
|
+
on pins predating #1344.
|
|
20
|
+
|
|
21
|
+
### Performance — engine sync (1.9.237)
|
|
22
|
+
|
|
23
|
+
- Vendored libleptris 1.9.237: typed atoms for QT3 equality
|
|
24
|
+
(typed constructors, float32, the xs: constructor surface),
|
|
25
|
+
per-function equality semantics (index-of/distinct-values/
|
|
26
|
+
deep-equal), the F&O fn:subsequence window with NaN safety and
|
|
27
|
+
member-type preservation, the xpath per-eval registry init fix,
|
|
28
|
+
QT3 slice 3 (405 gated cases), the matched-shape traversal
|
|
29
|
+
benchmark correction (#1341: the structural gap vs libxml2 is
|
|
30
|
+
1.5-1.7x, not 1.8-4.2x), and the single-crossing element
|
|
31
|
+
constructor (#1344).
|
|
32
|
+
|
|
8
33
|
## [1.9.232.0] - 2026-09-24
|
|
9
34
|
|
|
10
35
|
### Added
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.237"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -42,6 +42,8 @@ typedef size_t (*node_offset_fn)(void *);
|
|
|
42
42
|
typedef const char *(*element_text_fn)(void *);
|
|
43
43
|
typedef void *(*doc_create_fn)(void);
|
|
44
44
|
typedef void *(*elem_create_fn)(void *, const char *);
|
|
45
|
+
typedef void *(*elem_new_attrs_fn)(void *, const char *, const char **,
|
|
46
|
+
const char **, size_t);
|
|
45
47
|
typedef void *(*text_create_fn)(void *, const char *);
|
|
46
48
|
typedef void *(*create_child_fn)(void *, const char *);
|
|
47
49
|
typedef int (*append_child_fn)(void *, void *);
|
|
@@ -75,10 +77,14 @@ static node_offset_fn f_node_offset;
|
|
|
75
77
|
static element_text_fn f_element_text;
|
|
76
78
|
static doc_create_fn f_doc_create;
|
|
77
79
|
static elem_create_fn f_elem_create;
|
|
80
|
+
static elem_new_attrs_fn f_elem_new_with_attrs;
|
|
78
81
|
static text_create_fn f_text_create;
|
|
79
82
|
static create_child_fn f_create_child;
|
|
80
83
|
static append_child_fn f_append_child;
|
|
81
84
|
static append_child_fn f_prepend_child, f_insert_after, f_insert_before;
|
|
85
|
+
typedef void *(*elem_new_attrs_fn)(void *, const char *, const char **,
|
|
86
|
+
const char **, size_t);
|
|
87
|
+
static elem_new_attrs_fn f_elem_new_with_attrs;
|
|
82
88
|
static set_attr_fn f_set_attr;
|
|
83
89
|
typedef int (*xp_type_fn)(void *);
|
|
84
90
|
typedef void (*xp_free_fn)(void *);
|
|
@@ -218,6 +224,11 @@ static void resolve_symbols(const char *lib_path)
|
|
|
218
224
|
f_element_text = (element_text_fn)lib_sym(h, "leptris_element_text");
|
|
219
225
|
f_doc_create = (doc_create_fn)lib_sym(h, "leptris_document_create");
|
|
220
226
|
f_elem_create = (elem_create_fn)lib_sym(h, "leptris_element_create");
|
|
227
|
+
/* Single-crossing element construction (libleptris 1.9.237,
|
|
228
|
+
* #1344): NULL in older engine pins; the create path falls
|
|
229
|
+
* back to create + per-pair sets. */
|
|
230
|
+
f_elem_new_with_attrs =
|
|
231
|
+
(elem_new_attrs_fn)lib_sym(h, "leptris_element_new_with_attributes");
|
|
221
232
|
f_text_create = (text_create_fn)lib_sym(h, "leptris_text_node_create");
|
|
222
233
|
f_create_child = (create_child_fn)lib_sym(h, "leptris_element_create_child");
|
|
223
234
|
f_append_child = (append_child_fn)lib_sym(h, "leptris_element_append_child");
|
|
@@ -225,6 +236,11 @@ static void resolve_symbols(const char *lib_path)
|
|
|
225
236
|
f_insert_after = (append_child_fn)lib_sym(h, "leptris_element_insert_after");
|
|
226
237
|
f_insert_before = (append_child_fn)lib_sym(h, "leptris_element_insert_before");
|
|
227
238
|
f_set_attr = (set_attr_fn)lib_sym(h, "leptris_element_set_attribute");
|
|
239
|
+
/* Engine single-crossing construction (leptris#1344, 1.9.236+):
|
|
240
|
+
* optional; absent engines use the create_child + set_attr
|
|
241
|
+
* loop, which is still one Ruby->C crossing. */
|
|
242
|
+
f_elem_new_with_attrs =
|
|
243
|
+
(elem_new_attrs_fn)lib_sym(h, "leptris_element_new_with_attributes");
|
|
228
244
|
f_xp_type = (xp_type_fn)lib_sym(h, "leptris_xpath_result_type");
|
|
229
245
|
f_xp_free = (xp_free_fn)lib_sym(h, "leptris_xpath_result_free");
|
|
230
246
|
f_first_child = (first_child_fn)lib_sym(h, "leptris_node_first_child");
|
|
@@ -1638,15 +1654,88 @@ static VALUE nf_bulk_attributes(VALUE self, VALUE addr)
|
|
|
1638
1654
|
/* ---- Binding create via C (TODO.perf/09): one call creates the
|
|
1639
1655
|
* node AND constructs the binding wrapper (class dispatch, ivars,
|
|
1640
1656
|
* identity-cache store) — no FFI marshaling, no wrap_fresh path. */
|
|
1657
|
+
static int set_attr_pair(VALUE key, VALUE value, VALUE data)
|
|
1658
|
+
{
|
|
1659
|
+
void *elem =
|
|
1660
|
+
(void *)(uintptr_t)NUM2ULL(rb_ary_entry(data, 0));
|
|
1661
|
+
VALUE k = RB_TYPE_P(key, T_STRING) ? key : rb_obj_as_string(key);
|
|
1662
|
+
VALUE v = NIL_P(value) ? rb_usascii_str_new(0, 0)
|
|
1663
|
+
: (RB_TYPE_P(value, T_STRING) ? value
|
|
1664
|
+
: rb_obj_as_string(value));
|
|
1665
|
+
|
|
1666
|
+
rb_ary_push(rb_ary_entry(data, 1), k);
|
|
1667
|
+
rb_ary_push(rb_ary_entry(data, 2), v);
|
|
1668
|
+
f_set_attr(elem, RSTRING_PTR(k), RSTRING_PTR(v));
|
|
1669
|
+
return ST_CONTINUE;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1641
1672
|
static VALUE nf_create_binding_element(VALUE self, VALUE document,
|
|
1642
|
-
VALUE name)
|
|
1673
|
+
VALUE name, VALUE attrs)
|
|
1643
1674
|
{
|
|
1644
1675
|
void *doc = doc_ptr_of(document);
|
|
1645
|
-
void *ptr
|
|
1676
|
+
void *ptr;
|
|
1677
|
+
VALUE name_str = StringValue(name);
|
|
1646
1678
|
VALUE node, cache, key;
|
|
1647
1679
|
|
|
1648
1680
|
(void)self;
|
|
1649
1681
|
resolve_binding_classes();
|
|
1682
|
+
if (attrs == Qnil ||
|
|
1683
|
+
(RB_TYPE_P(attrs, T_HASH) && RHASH_SIZE(attrs) == 0)) {
|
|
1684
|
+
ptr = f_elem_create(doc, RSTRING_PTR(name_str));
|
|
1685
|
+
} else if (f_elem_new_with_attrs != NULL) {
|
|
1686
|
+
/* #1344: element + every attribute in ONE crossing.
|
|
1687
|
+
* Duplicate names replace, last wins; NULL values store
|
|
1688
|
+
* the empty string — engine semantics, identical to
|
|
1689
|
+
* per-pair set_attribute.
|
|
1690
|
+
*
|
|
1691
|
+
* Iteration goes through keys + aref (no rb_hash_foreach
|
|
1692
|
+
* state pointer): every converted key/value is pushed into
|
|
1693
|
+
* Ruby arrays BEFORE its pointer enters the C arrays, so
|
|
1694
|
+
* temporaries (non-String keys, nil -> "") survive until
|
|
1695
|
+
* the engine call returns — no C pointer rides through a
|
|
1696
|
+
* boxed VALUE (long is 32-bit on Windows/LLP64; the
|
|
1697
|
+
* foreach+boxed-struct shape truncated it and segfaulted
|
|
1698
|
+
* the windows CI leg). */
|
|
1699
|
+
VALUE keys, names, values;
|
|
1700
|
+
const char **np, **vp;
|
|
1701
|
+
long cap, i;
|
|
1702
|
+
Check_Type(attrs, T_HASH);
|
|
1703
|
+
cap = (long)RHASH_SIZE(attrs);
|
|
1704
|
+
keys = rb_funcall(attrs, rb_intern("keys"), 0);
|
|
1705
|
+
names = rb_ary_new_capa(cap);
|
|
1706
|
+
values = rb_ary_new_capa(cap);
|
|
1707
|
+
np = ruby_xmalloc(sizeof(char *) * (cap + 1));
|
|
1708
|
+
vp = ruby_xmalloc(sizeof(char *) * (cap + 1));
|
|
1709
|
+
for (i = 0; i < cap; i++) {
|
|
1710
|
+
VALUE k = rb_ary_entry(keys, i);
|
|
1711
|
+
VALUE v = rb_hash_aref(attrs, k);
|
|
1712
|
+
k = RB_TYPE_P(k, T_STRING) ? k : rb_obj_as_string(k);
|
|
1713
|
+
v = NIL_P(v) ? rb_usascii_str_new(0, 0)
|
|
1714
|
+
: (RB_TYPE_P(v, T_STRING) ? v
|
|
1715
|
+
: rb_obj_as_string(v));
|
|
1716
|
+
rb_ary_push(names, k);
|
|
1717
|
+
rb_ary_push(values, v);
|
|
1718
|
+
np[i] = RSTRING_PTR(k);
|
|
1719
|
+
vp[i] = RSTRING_PTR(v);
|
|
1720
|
+
}
|
|
1721
|
+
ptr = f_elem_new_with_attrs(doc, RSTRING_PTR(name_str),
|
|
1722
|
+
np, vp, (size_t)cap);
|
|
1723
|
+
ruby_xfree(np);
|
|
1724
|
+
ruby_xfree(vp);
|
|
1725
|
+
} else {
|
|
1726
|
+
/* Older pin: create + per-pair sets (N+1 crossings). */
|
|
1727
|
+
VALUE addr = Qnil;
|
|
1728
|
+
VALUE anchor;
|
|
1729
|
+
Check_Type(attrs, T_HASH);
|
|
1730
|
+
anchor = rb_ary_new_capa((long)RHASH_SIZE(attrs));
|
|
1731
|
+
ptr = f_elem_create(doc, RSTRING_PTR(name_str));
|
|
1732
|
+
if (!ptr)
|
|
1733
|
+
return Qnil;
|
|
1734
|
+
addr = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
1735
|
+
rb_hash_foreach(attrs, set_attr_pair,
|
|
1736
|
+
rb_ary_new3(3, addr, anchor,
|
|
1737
|
+
rb_ary_new_capa(0)));
|
|
1738
|
+
}
|
|
1650
1739
|
if (!ptr)
|
|
1651
1740
|
return Qnil; /* caller raises with the error channel */
|
|
1652
1741
|
node = rb_obj_alloc(c_b_element);
|
|
@@ -2194,6 +2283,56 @@ static VALUE nf_plan_structs_typed_p(VALUE self)
|
|
|
2194
2283
|
* to the per-attribute FFI chain (first/next/get_name/get_value,
|
|
2195
2284
|
* 4 crossings each) — the win amplifies with attribute density
|
|
2196
2285
|
* (40-attr elements: ~7x on the listing). */
|
|
2286
|
+
/* ---- Single-crossing element construction (leptris#1344) --------
|
|
2287
|
+
* Creates an element attached under parent (parent_addr 0 = free
|
|
2288
|
+
*floating, owner document only) with all attributes set — ONE
|
|
2289
|
+
* Ruby->C crossing for the whole construction. Internal calls are
|
|
2290
|
+
* C->C dlsym dispatches (create_child = create+attach in one
|
|
2291
|
+
* engine call; set_attr per attribute). Returns the new node's
|
|
2292
|
+
* address for wrapper minting. */
|
|
2293
|
+
static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
|
|
2294
|
+
VALUE parent_addr, VALUE name,
|
|
2295
|
+
VALUE attrs)
|
|
2296
|
+
{
|
|
2297
|
+
(void)self;
|
|
2298
|
+
(void)document;
|
|
2299
|
+
void *parent = NIL_P(parent_addr)
|
|
2300
|
+
? NULL
|
|
2301
|
+
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
2302
|
+
const char *cname = StringValueCStr(name);
|
|
2303
|
+
|
|
2304
|
+
if (f_elem_new_with_attrs && parent && RARRAY_LEN(attrs) > 0 &&
|
|
2305
|
+
RARRAY_LEN(attrs) % 2 == 0) {
|
|
2306
|
+
/* Engine single-crossing construction (1.9.236+): arena
|
|
2307
|
+
* create + all attributes with no per-attr calls at all. */
|
|
2308
|
+
long n = RARRAY_LEN(attrs) / 2;
|
|
2309
|
+
const char **names = ALLOCA_N(const char *, n);
|
|
2310
|
+
const char **values = ALLOCA_N(const char *, n);
|
|
2311
|
+
for (long i = 0; i < n; i++) {
|
|
2312
|
+
/* Hoist to lvalues: truffleruby's StringValueCStr takes
|
|
2313
|
+
* the address of its argument (RARRAY_AREF is not one). */
|
|
2314
|
+
VALUE an = RARRAY_AREF(attrs, 2 * i);
|
|
2315
|
+
VALUE av = RARRAY_AREF(attrs, 2 * i + 1);
|
|
2316
|
+
names[i] = StringValueCStr(an);
|
|
2317
|
+
values[i] = StringValueCStr(av);
|
|
2318
|
+
}
|
|
2319
|
+
void *elem = f_elem_new_with_attrs(parent, cname, names, values,
|
|
2320
|
+
n);
|
|
2321
|
+
return elem ? ULL2NUM((uintptr_t)elem) : Qnil;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
void *elem = parent ? f_create_child(parent, cname) : NULL;
|
|
2325
|
+
if (!elem) return Qnil;
|
|
2326
|
+
|
|
2327
|
+
long i, n = RARRAY_LEN(attrs);
|
|
2328
|
+
for (i = 0; i + 1 < n; i += 2) {
|
|
2329
|
+
VALUE an = RARRAY_AREF(attrs, i);
|
|
2330
|
+
VALUE av = RARRAY_AREF(attrs, i + 1);
|
|
2331
|
+
f_set_attr(elem, StringValueCStr(an), StringValueCStr(av));
|
|
2332
|
+
}
|
|
2333
|
+
return ULL2NUM((uintptr_t)elem);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2197
2336
|
static VALUE nf_attribute_rows(VALUE self, VALUE document, VALUE addr)
|
|
2198
2337
|
{
|
|
2199
2338
|
(void)self;
|
|
@@ -2808,6 +2947,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2808
2947
|
nf_plan_structs, 3);
|
|
2809
2948
|
rb_define_module_function(m_native, "attribute_rows",
|
|
2810
2949
|
nf_attribute_rows, 2);
|
|
2950
|
+
rb_define_module_function(m_native, "create_element_with_attrs",
|
|
2951
|
+
nf_create_element_with_attrs, 4);
|
|
2811
2952
|
rb_define_module_function(m_native, "plan_structs_typed?",
|
|
2812
2953
|
nf_plan_structs_typed_p, 0);
|
|
2813
2954
|
rb_define_module_function(m_native, "doc_handle_attach",
|
|
@@ -2822,7 +2963,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2822
2963
|
rb_define_module_function(m_native, "bulk_attr_faces",
|
|
2823
2964
|
nf_bulk_attr_faces, 2);
|
|
2824
2965
|
rb_define_module_function(m_native, "create_binding_element",
|
|
2825
|
-
nf_create_binding_element,
|
|
2966
|
+
nf_create_binding_element, 3);
|
|
2826
2967
|
rb_define_module_function(m_native, "create_binding_text",
|
|
2827
2968
|
nf_create_binding_text, 2);
|
|
2828
2969
|
rb_define_module_function(m_native, "bulk_children", nf_bulk_children, 2);
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -389,15 +389,32 @@ class Leptris::XML::Document
|
|
|
389
389
|
element
|
|
390
390
|
end
|
|
391
391
|
|
|
392
|
-
|
|
392
|
+
# attributes: optional Hash of {name => value} applied in the
|
|
393
|
+
# SAME engine crossing as construction
|
|
394
|
+
# (leptris_element_new_with_attributes, #1344) — builder paths
|
|
395
|
+
# collapse from 1 + N crossings to one. nil values store the
|
|
396
|
+
# empty string; duplicate names replace, last wins (engine
|
|
397
|
+
# semantics, identical to per-pair set_attribute).
|
|
398
|
+
def create_element(name, attributes = nil)
|
|
393
399
|
# TODO.perf/09: one C call creates + wraps when the ext is
|
|
394
400
|
# loaded — no FFI marshaling, no wrap_fresh frames.
|
|
395
401
|
if defined?(Leptris::XML::NATIVE_FAST)
|
|
396
|
-
node = Leptris::XML::Native.create_binding_element(
|
|
402
|
+
node = Leptris::XML::Native.create_binding_element(
|
|
403
|
+
self, name.to_s, attributes)
|
|
397
404
|
raise Leptris::XML::Error, "leptris_element_create failed" if node.nil?
|
|
398
405
|
return node
|
|
399
406
|
end
|
|
400
|
-
ptr =
|
|
407
|
+
ptr =
|
|
408
|
+
if attributes && !attributes.empty?
|
|
409
|
+
names = attributes.map { |k, _| k.to_s }
|
|
410
|
+
values = attributes.map { |_, v| v.nil? ? "" : v.to_s }
|
|
411
|
+
nb, na = Leptris::XML::CStringArray.to_c(names)
|
|
412
|
+
vb, va = Leptris::XML::CStringArray.to_c(values)
|
|
413
|
+
Leptris::XML::FFI.leptris_element_new_with_attributes(
|
|
414
|
+
c_ptr, name.to_s, nb, vb, names.size)
|
|
415
|
+
else
|
|
416
|
+
Leptris::XML::FFI.leptris_element_create(c_ptr, name)
|
|
417
|
+
end
|
|
401
418
|
raise Leptris::XML::Error, "leptris_element_create failed" if ptr.null?
|
|
402
419
|
Leptris::XML::Node.wrap_fresh(ptr, self, Leptris::XML::FFI::NODE_ELEMENT)
|
|
403
420
|
end
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -716,6 +716,13 @@ attach_function :leptris_parse_string,
|
|
|
716
716
|
attach_function :leptris_element_previous_sibling_any,
|
|
717
717
|
[:leptris_element], :leptris_element
|
|
718
718
|
|
|
719
|
+
# Single-crossing element construction (libleptris 1.9.237,
|
|
720
|
+
# #1344): element + every attribute in one C call. The name
|
|
721
|
+
# and value arrays are count-bounded char** (the NULL
|
|
722
|
+
# terminators CStringArray appends are simply never read).
|
|
723
|
+
attach_function :leptris_element_new_with_attributes,
|
|
724
|
+
[:leptris_document, :string, :pointer, :pointer, :size_t],
|
|
725
|
+
:leptris_element
|
|
719
726
|
attach_function :leptris_element_create,
|
|
720
727
|
[:leptris_document, :string], :leptris_element
|
|
721
728
|
attach_function :leptris_element_set_name,
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.237] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
<!-- Edit this section with the actual release notes. -->
|
|
6
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- dom: `leptris_element_new_with_attributes` — single-crossing
|
|
11
|
+
element construction (#1344). One call creates the element in the
|
|
12
|
+
document arena with every attribute attached — identical per-pair
|
|
13
|
+
semantics to leptris_element_set_attribute (duplicate names
|
|
14
|
+
replace, last wins; NULL values store the empty string). Builder
|
|
15
|
+
paths collapse from 1 + N crossings plus wrapper allocations per
|
|
16
|
+
node to a single crossing.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [1.9.236] - 2026-09-24
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- xpath: fn:subsequence implements the F&O window in position space —
|
|
24
|
+
NaN start/length yields empty, the window is NaN-safe including
|
|
25
|
+
-INF + INF = NaN, positions are never clamped to 1, the 2-arg form
|
|
26
|
+
is unbounded, and start/length round half-up like fn:round
|
|
27
|
+
(cbcl-subsequence-001..009, K2-5).
|
|
28
|
+
- xpath: boolean and xs:decimal sequence members carry their type
|
|
29
|
+
(\x03B/\x03D markers) — a false boolean stays falsy through
|
|
30
|
+
fn:subsequence/fn:remove round-trips (EBV reads the marker) and
|
|
31
|
+
xs:decimal equality compares lexically where the double carrier
|
|
32
|
+
cannot hold 30 significant digits.
|
|
33
|
+
- xpath: fn:subsequence and fn:remove preserve member types, so
|
|
34
|
+
`instance of`, deep-equal and effective boolean value agree across
|
|
35
|
+
the round-trip.
|
|
36
|
+
- QT3 runner: assert-permutation evaluates expected items as
|
|
37
|
+
expressions.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- QT3 corpus: subsequence 51→61, distinct-values 84→87, remove
|
|
42
|
+
21→23 adopted; typed-atom pins 86→67.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## [1.9.235] - 2026-09-24
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- xpath: typed atoms for QT3 equality — an atomic_type channel on
|
|
51
|
+
XPath results (typed constructors carry interned type names),
|
|
52
|
+
float32 precision for xs:float (float(1.01) ne double(1.01) while
|
|
53
|
+
decimal-vs-float promotes per F&O rank), typed \x03F sequence
|
|
54
|
+
markers, and the missing constructor surface (xs:untypedAtomic,
|
|
55
|
+
xs:hexBinary, xs:base64Binary, xs:gYear/gYearMonth/gMonthDay/
|
|
56
|
+
gMonth/gDay).
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- xpath: per-function equality semantics — fn:index-of never matches
|
|
61
|
+
NaN and does not coerce numbers to strings; fn:distinct-values
|
|
62
|
+
dedups NaN with itself and keeps the first spelling
|
|
63
|
+
(distinct((1, 2.0, 3, 2)) = 1, 2.0, 3); fn:reverse, fn:index-of and
|
|
64
|
+
fn:distinct-values preserve sequence-member types.
|
|
65
|
+
- xpath: fn:deep-equal ignores comment/PI children (F&O 3.0),
|
|
66
|
+
compares typed string classes (xs:date vs string = false), and
|
|
67
|
+
accepts the 3-argument collation form.
|
|
68
|
+
- QT3 corpus: deep-equal 180→192, distinct-values 74→84, index-of
|
|
69
|
+
34→42, reverse 53→57 adopted (125 typed-atom pins → 86).
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## [1.9.234] - 2026-09-24
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- benchmarks: the DOM Tree Traversal row now walks the documented
|
|
78
|
+
O(1) iteration shape (`leptris_element_first_child_any` +
|
|
79
|
+
`leptris_element_next_sibling_any`). The previous shape used
|
|
80
|
+
`child_count()` + indexed `child(elem, i)` — O(N^2/2) re-seeking
|
|
81
|
+
per parent — which measured the indexed-lookup algorithm instead of
|
|
82
|
+
per-hop engine cost, overstating the libxml2 gap at 1.8-4.2x.
|
|
83
|
+
Matched-shape gap vs libxml2 is 1.5-1.7x (#1341, closed as the
|
|
84
|
+
structural cost of the 64-byte compact node design).
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## [1.9.233] - 2026-09-24
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- QT3 corpus slice 3 — empty/exists/head/tail/count, 405 gated cases (xquery)
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- initialize registry_borrowed on every context init (xpath)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
3
100
|
## [1.9.232] - 2026-09-24
|
|
4
101
|
|
|
5
102
|
### Added
|
|
@@ -591,9 +688,15 @@ W3C QT3 suite as the gate:
|
|
|
591
688
|
<!-- Edit this section with the actual release notes. -->
|
|
592
689
|
<!-- See https://keepachangelog.com for format guidance. -->
|
|
593
690
|
|
|
594
|
-
###
|
|
691
|
+
### Added
|
|
595
692
|
|
|
596
|
-
-
|
|
693
|
+
- dom: `leptris_element_new_with_attributes` — single-crossing
|
|
694
|
+
element construction (#1344). One call creates the element in the
|
|
695
|
+
document arena with every attribute attached — identical per-pair
|
|
696
|
+
semantics to leptris_element_set_attribute (duplicate names
|
|
697
|
+
replace, last wins; NULL values store the empty string). Builder
|
|
698
|
+
paths collapse from 1 + N crossings plus wrapper allocations per
|
|
699
|
+
node to a single crossing.
|
|
597
700
|
|
|
598
701
|
|
|
599
702
|
## [1.9.200] - 2026-09-18
|
|
@@ -8293,9 +8396,15 @@ fall back to the legacy parser.
|
|
|
8293
8396
|
<!-- Edit this section with the actual release notes. -->
|
|
8294
8397
|
<!-- See https://keepachangelog.com for format guidance. -->
|
|
8295
8398
|
|
|
8296
|
-
###
|
|
8399
|
+
### Added
|
|
8297
8400
|
|
|
8298
|
-
-
|
|
8401
|
+
- dom: `leptris_element_new_with_attributes` — single-crossing
|
|
8402
|
+
element construction (#1344). One call creates the element in the
|
|
8403
|
+
document arena with every attribute attached — identical per-pair
|
|
8404
|
+
semantics to leptris_element_set_attribute (duplicate names
|
|
8405
|
+
replace, last wins; NULL values store the empty string). Builder
|
|
8406
|
+
paths collapse from 1 + N crossings plus wrapper allocations per
|
|
8407
|
+
node to a single crossing.
|
|
8299
8408
|
|
|
8300
8409
|
|
|
8301
8410
|
All notable changes to Leptris will be documented in this file.
|
|
@@ -35,7 +35,14 @@ typedef struct {
|
|
|
35
35
|
LeptrisDocument doc;
|
|
36
36
|
} doc_ctx_t;
|
|
37
37
|
|
|
38
|
-
/* Recursive tree traversal helper
|
|
38
|
+
/* Recursive tree traversal helper.
|
|
39
|
+
*
|
|
40
|
+
* Walks the same shape as the libxml2 bench (hop EVERY child via the
|
|
41
|
+
* sibling link, recurse into elements): first_child_any +
|
|
42
|
+
* next_sibling_any is the documented O(1)-per-step iteration. The
|
|
43
|
+
* previous shape — child_count() + child(elem, i) — re-seeked the
|
|
44
|
+
* child list from its head per access, O(N²/2) hops per parent, which
|
|
45
|
+
* measured the indexed-lookup algorithm, not per-hop engine cost. */
|
|
39
46
|
static void traverse_tree(LeptrisElement elem) {
|
|
40
47
|
if (!elem) return;
|
|
41
48
|
|
|
@@ -43,11 +50,12 @@ static void traverse_tree(LeptrisElement elem) {
|
|
|
43
50
|
const char* name = leptris_element_name(elem);
|
|
44
51
|
(void)name;
|
|
45
52
|
|
|
46
|
-
/* Traverse children */
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
/* Traverse children (hop every child; recurse into elements) */
|
|
54
|
+
for (LeptrisElement child = leptris_element_first_child_any(elem);
|
|
55
|
+
child; child = leptris_element_next_sibling_any(child)) {
|
|
56
|
+
if (leptris_node_get_type((LeptrisNodeRef)child) ==
|
|
57
|
+
LEPTRIS_NODE_TYPE_ELEMENT)
|
|
58
|
+
traverse_tree(child);
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -1984,6 +1984,33 @@ LEPTRIS_API size_t leptris_element_hash_value(LeptrisElement elem);
|
|
|
1984
1984
|
*/
|
|
1985
1985
|
LEPTRIS_API LeptrisElement leptris_element_create(LeptrisDocument doc, const char* name);
|
|
1986
1986
|
|
|
1987
|
+
/**
|
|
1988
|
+
* Create a new element with attributes set in one call (#1344)
|
|
1989
|
+
*
|
|
1990
|
+
* The fused twin of create + N x set_attribute: one crossing
|
|
1991
|
+
* builds the element with every attribute attached — the
|
|
1992
|
+
* builder-shape answer for programmatic document construction.
|
|
1993
|
+
* Attribute semantics are identical to leptris_element_set_attribute
|
|
1994
|
+
* per pair (same name handling, same value copy); a duplicate name
|
|
1995
|
+
* replaces, last wins.
|
|
1996
|
+
*
|
|
1997
|
+
* @param doc Document that will own the element
|
|
1998
|
+
* @param name Element name (QName prefixes handled like
|
|
1999
|
+
* leptris_element_create)
|
|
2000
|
+
* @param attr_names Attribute names; may be NULL when attr_count
|
|
2001
|
+
* is 0. A NULL element or NULL name string fails
|
|
2002
|
+
* the call.
|
|
2003
|
+
* @param attr_values Attribute values; a NULL entry stores the
|
|
2004
|
+
* empty string
|
|
2005
|
+
* @param attr_count Number of name/value pairs
|
|
2006
|
+
* @return New element or NULL on error
|
|
2007
|
+
*
|
|
2008
|
+
* Memory: Element owned by document, freed when document freed
|
|
2009
|
+
*/
|
|
2010
|
+
LEPTRIS_API LeptrisElement leptris_element_new_with_attributes(
|
|
2011
|
+
LeptrisDocument doc, const char* name, const char** attr_names,
|
|
2012
|
+
const char** attr_values, size_t attr_count);
|
|
2013
|
+
|
|
1987
2014
|
/**
|
|
1988
2015
|
* Set element name (rename element tag)
|
|
1989
2016
|
*
|
|
@@ -232,6 +232,26 @@ LeptrisElement leptris_element_create(LeptrisDocument doc, const char* name) {
|
|
|
232
232
|
return elem;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/* #1344: fused create + attribute-set — one crossing for the
|
|
236
|
+
* builder path. Identical per-pair semantics to
|
|
237
|
+
* leptris_element_set_attribute. */
|
|
238
|
+
LeptrisElement leptris_element_new_with_attributes(
|
|
239
|
+
LeptrisDocument doc, const char* name, const char** attr_names,
|
|
240
|
+
const char** attr_values, size_t attr_count) {
|
|
241
|
+
LeptrisElement elem = leptris_element_create(doc, name);
|
|
242
|
+
if (!elem) return NULL;
|
|
243
|
+
if (attr_count && !attr_names) return NULL;
|
|
244
|
+
for (size_t i = 0; i < attr_count; i++) {
|
|
245
|
+
if (!attr_names[i]) return NULL;
|
|
246
|
+
if (leptris_element_set_attribute(
|
|
247
|
+
elem, attr_names[i],
|
|
248
|
+
(attr_values && attr_values[i]) ? attr_values[i] : "") !=
|
|
249
|
+
LEPTRIS_OK)
|
|
250
|
+
return NULL;
|
|
251
|
+
}
|
|
252
|
+
return elem;
|
|
253
|
+
}
|
|
254
|
+
|
|
235
255
|
/**
|
|
236
256
|
* Append child element (Public API)
|
|
237
257
|
*/
|
|
@@ -649,6 +649,11 @@ struct leptris_xpath_result {
|
|
|
649
649
|
* free-list. */
|
|
650
650
|
int is_int;
|
|
651
651
|
long long int_value;
|
|
652
|
+
/* Typed-atom tag (QT3 slice): interned type name ("xs:date",
|
|
653
|
+
* "xs:float", ...) when the atom came from a typed constructor,
|
|
654
|
+
* NULL for the untyped/string class. NOT owned — static
|
|
655
|
+
* literals only. Cleared in result_new (free-list recycle). */
|
|
656
|
+
const char* atomic_type;
|
|
652
657
|
};
|
|
653
658
|
|
|
654
659
|
/* Namespace mapping for XPath context (v0.8.0) */
|
|
@@ -126,6 +126,12 @@ void xpath_context_init(XPathContext* context,
|
|
|
126
126
|
context->input_len = 0;
|
|
127
127
|
|
|
128
128
|
extern XPathFunctionRegistry* leptris_xpath_build_custom_registry(struct leptris_document*);
|
|
129
|
+
/* The borrow flag MUST be established on every init: the
|
|
130
|
+
* context is stack storage and cleanup's registry-free is
|
|
131
|
+
* gated on this flag — stale stack bytes read as borrowed=1
|
|
132
|
+
* and leak the per-eval XQuery registry (the common no-custom
|
|
133
|
+
* -fns path never wrote the flag). */
|
|
134
|
+
context->registry_borrowed = 0;
|
|
129
135
|
context->function_registry = leptris_xpath_build_custom_registry(document);
|
|
130
136
|
if (!context->function_registry) {
|
|
131
137
|
context->function_registry = xpath_function_registry_get_standard();
|
|
@@ -736,6 +742,7 @@ struct leptris_xpath_result* xpath_result_new(XPathResultType type) {
|
|
|
736
742
|
|
|
737
743
|
result->type = type;
|
|
738
744
|
result->is_int = 0; /* free-list recycling would leak it */
|
|
745
|
+
result->atomic_type = NULL;
|
|
739
746
|
|
|
740
747
|
/* Initialize union based on type. XPATH_RESULT_CACHED is the
|
|
741
748
|
* free-list sentinel — a fresh result never carries it. */
|