leptris 1.9.313.0-aarch64-linux → 1.9.314.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +22 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/cli/commands/xquery.c +13 -2
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +19 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +12 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +170 -3
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +15 -0
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +25 -8
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +70 -5
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1441abb54a4661971bb08a978104d410154ee5130f86dee8d908c9460aa15afc
|
|
4
|
+
data.tar.gz: 71dcba8c436d67438998248892a7579e50149f5242ef83574d17e028a4a210bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fac0c2e0e0ae04f0f3b59548310a92cdf9b17ef9e80f581006359f758e91e8997177061832c2b942af9ee3bf6a197e06f34e5627ea34cc24cb7cbda82d28890
|
|
7
|
+
data.tar.gz: 9eda38fb49f50c5b9e46578a9884cb99d7e5ac4cc9e8f25cea6c5573df8a33ae39c3a088dfafc8bd216f604a611cf9c25720ffee53d22063fa3b8b92ce5ddd3d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## [1.9.314.0] - 2026-10-07
|
|
2
|
+
|
|
3
|
+
### Changed — engine sync (libleptris 1.9.314)
|
|
4
|
+
|
|
5
|
+
- **XQuery element constructors yield real nodes (#181, engine
|
|
6
|
+
#1568)**: `<a>…</a>`, `element name {…}`, and PI/comment
|
|
7
|
+
constructors materialize into real documents and yield their
|
|
8
|
+
root ELEMENT node — identity, axis steps, and higher-order
|
|
9
|
+
callbacks see one stable node per evaluation, and constructed
|
|
10
|
+
elements ride sequences, maps, `fn:reverse`, `unordered`, and
|
|
11
|
+
`deep-equal` as markup instead of collapsing to their string
|
|
12
|
+
value. Consumers wanting the old string form serialize
|
|
13
|
+
(`result.first.to_xml`). Source-doc anchoring (#691) fixes an
|
|
14
|
+
order-dependent use-after-free; the (AST, content)-keyed memo
|
|
15
|
+
bounds per-reference re-materialization (the fold-recursion
|
|
16
|
+
stress peaks ~83MB, was 200GB unbounded). The three
|
|
17
|
+
constructor-result pins adopt the node-yielding contract.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Whitespace DOM shape pinned cross-platform (#1559, engine
|
|
22
|
+
#1569)**: the divergence investigation traced to the 1.9.311
|
|
23
|
+
linux-aarch64 prebuilt binary. The binding mirrors
|
|
24
|
+
`test_ws_shape` — every indentation run preserved, nested
|
|
25
|
+
bibitem shape, sole-child whitespace as content, CRLF→LF
|
|
26
|
+
normalization, no text nodes in collapsed spellings — so any
|
|
27
|
+
stale or diverging vendored binary goes red on its own runner
|
|
28
|
+
(the gem-smoke ubuntu-24.04-arm leg exercises the published
|
|
29
|
+
aarch64 gem).
|
|
30
|
+
|
|
1
31
|
## [1.9.313.0] - 2026-10-07
|
|
2
32
|
|
|
3
33
|
### Fixed — engine sync (libleptris 1.9.313)
|
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.314"
|
|
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/lib/leptris/version.rb
CHANGED
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.314] - 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **XQuery element constructors yield real nodes (#181, PR #1568).**
|
|
8
|
+
`<a>…</a>`, `element name {…}`, and PI/comment constructors
|
|
9
|
+
materialize into real documents and yield their root ELEMENT node
|
|
10
|
+
(the `document{}` pattern), so identity (`is`, `intersect`), axis
|
|
11
|
+
steps, and higher-order callbacks see one stable node per
|
|
12
|
+
evaluation. Constructed elements ride sequences, maps,
|
|
13
|
+
`fn:reverse`, `unordered`, and `deep-equal` as markup — the
|
|
14
|
+
string-value collapse that made every element look like its text
|
|
15
|
+
content is gone — and `leptris xquery` prints them serialized.
|
|
16
|
+
Materialized documents anchor on the source tree (the #691
|
|
17
|
+
discipline), fixing an order-dependent use-after-free when FLWOR
|
|
18
|
+
or window results outlive the evaluation context, and an
|
|
19
|
+
(AST, content)-keyed memo bounds per-reference re-materialization:
|
|
20
|
+
the fold-recursion stress peaks at ~83MB where the unbounded path
|
|
21
|
+
reached 200GB.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
3
25
|
## [1.9.313] - 2026-10-07
|
|
4
26
|
|
|
5
27
|
### Fixed
|
|
@@ -121,9 +121,20 @@ static cli_result_t xquery_run(int argc, char** argv) {
|
|
|
121
121
|
} else if (leptris_xpath_result_type(r) == LEPTRIS_XPATH_NODESET) {
|
|
122
122
|
size_t n = leptris_xpath_result_count(r);
|
|
123
123
|
for (size_t k = 0; k < n; k++) {
|
|
124
|
-
const char* v = leptris_xpath_result_node_value(r, k);
|
|
125
124
|
if (k) putchar(' ');
|
|
126
|
-
|
|
125
|
+
/* Constructed elements (#181) are real nodes now — print
|
|
126
|
+
* their serialized markup, not the string-value (the
|
|
127
|
+
* pre-node ctor spelling printed markup too). */
|
|
128
|
+
if (leptris_xpath_result_node_kind(r, k) ==
|
|
129
|
+
LEPTRIS_XPATH_NODE_ELEMENT) {
|
|
130
|
+
char* m = leptris_element_serialize(
|
|
131
|
+
leptris_xpath_result_get(r, k), NULL);
|
|
132
|
+
fputs(m ? m : "", stdout);
|
|
133
|
+
leptris_free_string(m);
|
|
134
|
+
} else {
|
|
135
|
+
const char* v = leptris_xpath_result_node_value(r, k);
|
|
136
|
+
fputs(v ? v : "", stdout);
|
|
137
|
+
}
|
|
127
138
|
}
|
|
128
139
|
if (n) putchar('\n');
|
|
129
140
|
} else {
|
|
@@ -723,6 +723,14 @@ typedef struct xpath_namespace_mapping {
|
|
|
723
723
|
char* uri; /* Namespace URI (required) */
|
|
724
724
|
} XPathNamespaceMapping;
|
|
725
725
|
|
|
726
|
+
/* #181: one materialized ctor doc per (AST occurrence, content). */
|
|
727
|
+
typedef struct xpath_ctor_cache_entry {
|
|
728
|
+
void* ast; /* ELEMENT_CTOR AST occurrence */
|
|
729
|
+
char* key; /* strdup of the serialized ctor form */
|
|
730
|
+
struct leptris_document* doc; /* borrowed — owned_docs owns it */
|
|
731
|
+
void* root; /* materialized root node */
|
|
732
|
+
} XPathCtorCacheEntry;
|
|
733
|
+
|
|
726
734
|
/* XPath context - Matches ext/leptris/xpath.h _xpath_context */
|
|
727
735
|
typedef struct xpath_context {
|
|
728
736
|
struct leptris_document* document;
|
|
@@ -787,6 +795,17 @@ typedef struct xpath_context {
|
|
|
787
795
|
struct leptris_document** owned_docs;
|
|
788
796
|
size_t n_owned_docs;
|
|
789
797
|
size_t cap_owned_docs;
|
|
798
|
+
/* #181 memo: ONE materialized ctor document per (AST
|
|
799
|
+
* occurrence, serialized content). Re-references of one
|
|
800
|
+
* binding (arg re-evaluation re-runs the ctor with identical
|
|
801
|
+
* content) reuse the cached node — allocation stays bounded by
|
|
802
|
+
* distinct occurrences, never by reference count (the 200GB
|
|
803
|
+
* recursion bomb). Loop-dependent ctors (`for $v in ... return
|
|
804
|
+
* <n>{$v}</n>`) serialize differently per iteration and stay
|
|
805
|
+
* distinct nodes. Docs stay owned by owned_docs; the cache
|
|
806
|
+
* only maps (ast, content) -> root. */
|
|
807
|
+
struct xpath_ctor_cache_entry* ctor_cache;
|
|
808
|
+
size_t n_ctor_cache, cap_ctor_cache;
|
|
790
809
|
|
|
791
810
|
/* XQuery `declare default element namespace` — unprefixed name
|
|
792
811
|
* tests match elements in THIS namespace, and unprefixed
|
|
@@ -147,6 +147,9 @@ void xpath_context_init(XPathContext* context,
|
|
|
147
147
|
context->owned_docs = NULL;
|
|
148
148
|
context->n_owned_docs = 0;
|
|
149
149
|
context->cap_owned_docs = 0;
|
|
150
|
+
context->ctor_cache = NULL;
|
|
151
|
+
context->n_ctor_cache = 0;
|
|
152
|
+
context->cap_ctor_cache = 0;
|
|
150
153
|
context->xquery_default_ns = NULL;
|
|
151
154
|
context->xquery_spelling = 0;
|
|
152
155
|
}
|
|
@@ -192,6 +195,15 @@ void xpath_context_cleanup(XPathContext* context) {
|
|
|
192
195
|
context->owned_docs = NULL;
|
|
193
196
|
context->n_owned_docs = 0;
|
|
194
197
|
context->cap_owned_docs = 0;
|
|
198
|
+
/* #181: the ctor memo borrows docs (owned_docs freed them);
|
|
199
|
+
* the cache owns only its strdup'd content keys. */
|
|
200
|
+
if (context->ctor_cache) {
|
|
201
|
+
for (size_t i = 0; i < context->n_ctor_cache; i++)
|
|
202
|
+
free(context->ctor_cache[i].key);
|
|
203
|
+
}
|
|
204
|
+
free(context->ctor_cache);
|
|
205
|
+
context->ctor_cache = NULL;
|
|
206
|
+
context->n_ctor_cache = context->cap_ctor_cache = 0;
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
const char* xpath_context_error(XPathContext* context) {
|
|
@@ -144,6 +144,22 @@ void leptris_dur_format(double secs, char* buf, size_t cap) {
|
|
|
144
144
|
|
|
145
145
|
extern char* get_node_text(void* node);
|
|
146
146
|
|
|
147
|
+
/* functions_ext31.c — #691 source-document anchoring (#181 ctor docs). */
|
|
148
|
+
extern int xq_anchor_on_source(XPathContext* ctx,
|
|
149
|
+
struct leptris_document* anchored);
|
|
150
|
+
|
|
151
|
+
/* #181: a materialized ctor node splices its MARKUP into a
|
|
152
|
+
* parent's serialized content — get_node_text would yield only its
|
|
153
|
+
* string value and the element would vanish. Non-element members
|
|
154
|
+
* keep the text fetch. Shared with the collect-items family
|
|
155
|
+
* (functions_ext31.c). */
|
|
156
|
+
char* ctor_member_markup(void* node) {
|
|
157
|
+
if (node && (int)XPATH_NODE_TYPE(node) ==
|
|
158
|
+
(int)LEPTRIS_NODE_TYPE_ELEMENT)
|
|
159
|
+
return leptris_element_serialize((LeptrisElement)node, NULL);
|
|
160
|
+
return get_node_text(node);
|
|
161
|
+
}
|
|
162
|
+
|
|
147
163
|
/* ============================================================================
|
|
148
164
|
* Operator Evaluation
|
|
149
165
|
* ============================================================================ */
|
|
@@ -1132,6 +1148,15 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
1132
1148
|
item->value.nodeset_value->count > 1) {
|
|
1133
1149
|
XPathNodeSet* sq = item->value.nodeset_value;
|
|
1134
1150
|
for (size_t si = 0; si < sq->count; si++) {
|
|
1151
|
+
/* #181: element members pass through as
|
|
1152
|
+
* NODES (identity preserved into the output
|
|
1153
|
+
* sequence); a text wrap would leave only
|
|
1154
|
+
* their string value. */
|
|
1155
|
+
if ((int)XPATH_NODE_TYPE(sq->nodes[si]) ==
|
|
1156
|
+
(int)LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
1157
|
+
xpath_nodeset_add(out, sq->nodes[si]);
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1135
1160
|
char* piece = get_node_text(sq->nodes[si]);
|
|
1136
1161
|
XPathTextNode* tn =
|
|
1137
1162
|
synth_text(piece ? piece : "",
|
|
@@ -1186,6 +1211,27 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
1186
1211
|
ctx->variable_set, loop_var);
|
|
1187
1212
|
continue;
|
|
1188
1213
|
}
|
|
1214
|
+
/* #181: a single ELEMENT member passes through as a
|
|
1215
|
+
* NODE (identity into the output sequence) — the
|
|
1216
|
+
* string wrap would leave its string value. Order-by
|
|
1217
|
+
* keeps the wrap (the ObItem collector stores text
|
|
1218
|
+
* nodes). */
|
|
1219
|
+
if (item->type == XPATH_RESULT_NODESET &&
|
|
1220
|
+
item->value.nodeset_value &&
|
|
1221
|
+
item->value.nodeset_value->count == 1 && !n_obk &&
|
|
1222
|
+
(int)XPATH_NODE_TYPE(
|
|
1223
|
+
item->value.nodeset_value->nodes[0]) ==
|
|
1224
|
+
(int)LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
1225
|
+
void* en = item->value.nodeset_value->nodes[0];
|
|
1226
|
+
xpath_result_free(item);
|
|
1227
|
+
xpath_nodeset_add(out, en);
|
|
1228
|
+
if (pos_var)
|
|
1229
|
+
xpath_variable_set_remove(
|
|
1230
|
+
ctx->variable_set, pos_var);
|
|
1231
|
+
xpath_variable_set_remove(
|
|
1232
|
+
ctx->variable_set, loop_var);
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1189
1235
|
char* piece =
|
|
1190
1236
|
(ctx->xquery_spelling &&
|
|
1191
1237
|
item->type == XPATH_RESULT_NUMBER && !item->is_int)
|
|
@@ -2127,7 +2173,7 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2127
2173
|
if (!v) continue;
|
|
2128
2174
|
if (v->type == XPATH_RESULT_NODESET && v->value.nodeset_value) {
|
|
2129
2175
|
for (size_t m = 0; m < v->value.nodeset_value->count; m++) {
|
|
2130
|
-
char* t =
|
|
2176
|
+
char* t = ctor_member_markup(v->value.nodeset_value->nodes[m]);
|
|
2131
2177
|
if (!t) continue;
|
|
2132
2178
|
while (len + strlen(t) + 1 > cap) { cap *= 2; buf = (char*)realloc(buf, cap); if (!buf) return NULL; }
|
|
2133
2179
|
memcpy(buf + len, t, strlen(t));
|
|
@@ -2427,8 +2473,9 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2427
2473
|
* (adjacent constructed items). */
|
|
2428
2474
|
for (size_t m = 0; m < v->value.nodeset_value->count;
|
|
2429
2475
|
m++) {
|
|
2430
|
-
char* t =
|
|
2431
|
-
|
|
2476
|
+
char* t =
|
|
2477
|
+
ctor_member_markup(
|
|
2478
|
+
v->value.nodeset_value->nodes[m]);
|
|
2432
2479
|
if (!t) continue;
|
|
2433
2480
|
for (const char* q = t; *q; q++) {
|
|
2434
2481
|
while (len + 8 > cap) { cap *= 2; buf = (char*)realloc(buf, cap); if (!buf) return NULL; }
|
|
@@ -2465,6 +2512,113 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2465
2512
|
while (len + strlen(name) + 4 > cap) { cap *= 2; buf = (char*)realloc(buf, cap); if (!buf) return NULL; }
|
|
2466
2513
|
len += (size_t)snprintf(buf + len, cap - len, "</%s>", name);
|
|
2467
2514
|
}
|
|
2515
|
+
/* #181: node materialization — the ctor's serialized form
|
|
2516
|
+
* parses into a REAL document (the document{} / fn:doc
|
|
2517
|
+
* pattern above): the ctor yields its root ELEMENT node,
|
|
2518
|
+
* so identity (is / intersect), axis steps, and HOF
|
|
2519
|
+
* callbacks all see one stable node per evaluation. The
|
|
2520
|
+
* document is anchored in the context's owned chain —
|
|
2521
|
+
* results borrow the handle, exactly like document{}.
|
|
2522
|
+
* Non-markup or parser-rejected content keeps the string
|
|
2523
|
+
* spelling. */
|
|
2524
|
+
{
|
|
2525
|
+
/* #181 memo: reuse what THIS (AST occurrence, content)
|
|
2526
|
+
* already materialized. Without it, re-evaluation of a
|
|
2527
|
+
* bound ctor re-runs it per reference and each run
|
|
2528
|
+
* anchors a fresh document — through fold recursion that
|
|
2529
|
+
* reached 200GB. Same occurrence + same serialized form
|
|
2530
|
+
* = the same binding's node (stable identity); a new
|
|
2531
|
+
* serialized form (next loop iteration) is a new node. */
|
|
2532
|
+
for (size_t mi = 0; mi < ctx->n_ctor_cache; mi++)
|
|
2533
|
+
if (ctx->ctor_cache[mi].ast == (void*)ast &&
|
|
2534
|
+
strcmp(ctx->ctor_cache[mi].key, buf) == 0) {
|
|
2535
|
+
XPathNodeSet* mns = xpath_nodeset_new();
|
|
2536
|
+
struct leptris_xpath_result* mr =
|
|
2537
|
+
mns ? xpath_result_new(XPATH_RESULT_NODESET)
|
|
2538
|
+
: NULL;
|
|
2539
|
+
if (mr) {
|
|
2540
|
+
mr->value.nodeset_value = mns;
|
|
2541
|
+
xpath_nodeset_add(
|
|
2542
|
+
mns, (LeptrisNode*)ctx->ctor_cache[mi].root);
|
|
2543
|
+
free(buf);
|
|
2544
|
+
return mr;
|
|
2545
|
+
}
|
|
2546
|
+
if (mns) xpath_nodeset_free(mns);
|
|
2547
|
+
break;
|
|
2548
|
+
}
|
|
2549
|
+
size_t blen = strlen(buf);
|
|
2550
|
+
LeptrisStatus pst = LEPTRIS_OK;
|
|
2551
|
+
LeptrisDocument pdoc =
|
|
2552
|
+
blen ? leptris_parse_string(buf, blen, &pst) : NULL;
|
|
2553
|
+
if (pdoc) {
|
|
2554
|
+
LeptrisElement root = leptris_document_root(pdoc);
|
|
2555
|
+
/* #181: anchor on the SOURCE document (the #691
|
|
2556
|
+
* discipline — fn:snapshot / analyze-string). The
|
|
2557
|
+
* ctx owned_docs chain dies at cleanup, BEFORE the
|
|
2558
|
+
* public eval returns, and FLWOR member capture
|
|
2559
|
+
* borrows ctor roots into the result sequence — the
|
|
2560
|
+
* materialized doc must outlive the result, so it
|
|
2561
|
+
* rides the source tree's anchored_docs chain
|
|
2562
|
+
* (released with the caller's document). */
|
|
2563
|
+
int anchored =
|
|
2564
|
+
root ? xq_anchor_on_source(ctx, pdoc) : -1;
|
|
2565
|
+
if (anchored == 0) {
|
|
2566
|
+
if (ctx->n_ctor_cache == ctx->cap_ctor_cache) {
|
|
2567
|
+
size_t nc = ctx->cap_ctor_cache
|
|
2568
|
+
? ctx->cap_ctor_cache * 2 : 8;
|
|
2569
|
+
XPathCtorCacheEntry* ne =
|
|
2570
|
+
(XPathCtorCacheEntry*)realloc(
|
|
2571
|
+
ctx->ctor_cache,
|
|
2572
|
+
nc * sizeof(XPathCtorCacheEntry));
|
|
2573
|
+
if (!ne) {
|
|
2574
|
+
/* No cache slot: the node is still
|
|
2575
|
+
* valid, just unmemoized. */
|
|
2576
|
+
XPathNodeSet* fns = xpath_nodeset_new();
|
|
2577
|
+
struct leptris_xpath_result* fout =
|
|
2578
|
+
fns ? xpath_result_new(
|
|
2579
|
+
XPATH_RESULT_NODESET)
|
|
2580
|
+
: NULL;
|
|
2581
|
+
if (fout) {
|
|
2582
|
+
fout->value.nodeset_value = fns;
|
|
2583
|
+
xpath_nodeset_add(fns, (LeptrisNode*)root);
|
|
2584
|
+
free(buf);
|
|
2585
|
+
return fout;
|
|
2586
|
+
}
|
|
2587
|
+
if (fns) xpath_nodeset_free(fns);
|
|
2588
|
+
} else {
|
|
2589
|
+
ctx->ctor_cache = ne;
|
|
2590
|
+
ctx->cap_ctor_cache = nc;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
if (ctx->n_ctor_cache < ctx->cap_ctor_cache) {
|
|
2594
|
+
char* nkey = leptris_strdup(buf);
|
|
2595
|
+
if (nkey) {
|
|
2596
|
+
ctx->ctor_cache[ctx->n_ctor_cache].ast =
|
|
2597
|
+
(void*)ast;
|
|
2598
|
+
ctx->ctor_cache[ctx->n_ctor_cache].key = nkey;
|
|
2599
|
+
ctx->ctor_cache[ctx->n_ctor_cache].doc = pdoc;
|
|
2600
|
+
ctx->ctor_cache[ctx->n_ctor_cache].root =
|
|
2601
|
+
(void*)root;
|
|
2602
|
+
ctx->n_ctor_cache++;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
XPathNodeSet* ns = xpath_nodeset_new();
|
|
2606
|
+
struct leptris_xpath_result* nout =
|
|
2607
|
+
ns ? xpath_result_new(XPATH_RESULT_NODESET)
|
|
2608
|
+
: NULL;
|
|
2609
|
+
if (nout) {
|
|
2610
|
+
nout->value.nodeset_value = ns;
|
|
2611
|
+
xpath_nodeset_add(ns, (LeptrisNode*)root);
|
|
2612
|
+
free(buf);
|
|
2613
|
+
return nout;
|
|
2614
|
+
}
|
|
2615
|
+
if (ns) xpath_nodeset_free(ns);
|
|
2616
|
+
return NULL;
|
|
2617
|
+
}
|
|
2618
|
+
if (!root) leptris_document_free(pdoc);
|
|
2619
|
+
/* anchor failure: the helper already freed pdoc */
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2468
2622
|
struct leptris_xpath_result* out =
|
|
2469
2623
|
xpath_result_new(XPATH_RESULT_STRING);
|
|
2470
2624
|
if (!out) { free(buf); return NULL; }
|
|
@@ -2615,6 +2769,19 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2615
2769
|
}
|
|
2616
2770
|
}
|
|
2617
2771
|
}
|
|
2772
|
+
/* Element values are REAL nodes now (#181): ride the
|
|
2773
|
+
* serialized MARKUP, not the string-value — otherwise
|
|
2774
|
+
* every <a>…</a> maps to its text ("23") and map
|
|
2775
|
+
* deep-equality cannot see attributes at all
|
|
2776
|
+
* (fn-deep-equal-maps-12). */
|
|
2777
|
+
if (!v && vr && vr->type == XPATH_RESULT_NODESET &&
|
|
2778
|
+
vr->value.nodeset_value &&
|
|
2779
|
+
vr->value.nodeset_value->count == 1 &&
|
|
2780
|
+
(int)XPATH_NODE_TYPE(vr->value.nodeset_value->nodes[0]) ==
|
|
2781
|
+
(int)LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
2782
|
+
v = ctor_member_markup(
|
|
2783
|
+
vr->value.nodeset_value->nodes[0]);
|
|
2784
|
+
}
|
|
2618
2785
|
if (!v && vr) v = xpath_to_string(vr);
|
|
2619
2786
|
if (vr) xpath_result_free(vr);
|
|
2620
2787
|
size_t kn = k ? strlen(k) : 0, vn = v ? strlen(v) : 0;
|
|
@@ -234,6 +234,8 @@ XPathFunctionDef* xpath_function_registry_get(
|
|
|
234
234
|
* This is declared in evaluator_internal.h and implemented in evaluator_types.c
|
|
235
235
|
*/
|
|
236
236
|
extern char* get_node_text(void* node);
|
|
237
|
+
/* evaluator_operators.c — element markup discipline (#181). */
|
|
238
|
+
extern char* ctor_member_markup(void* node);
|
|
237
239
|
extern char* xpath_number_to_string(double number);
|
|
238
240
|
extern char* xpath_int_to_string(long long v);
|
|
239
241
|
|
|
@@ -1140,6 +1142,19 @@ static size_t de_collect(struct leptris_xpath_result* r, DeItem* out,
|
|
|
1140
1142
|
out[i].kind = 2;
|
|
1141
1143
|
out[i].borrow = leptris_text_get_content(
|
|
1142
1144
|
(LeptrisTextNode*)ns->nodes[i]);
|
|
1145
|
+
} else if (ty == LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
1146
|
+
/* #181: ctor elements are real nodes now. Collect as
|
|
1147
|
+
* MARKUP so a constructed element compares against
|
|
1148
|
+
* the markup-string member every sequence function
|
|
1149
|
+
* emits (fn:reverse output) — de_markup_equal_n
|
|
1150
|
+
* reparses both sides into de_node_equal, so this is
|
|
1151
|
+
* the same compare, order-insensitive attrs and all. */
|
|
1152
|
+
out[i].kind = 2;
|
|
1153
|
+
out[i].str = ctor_member_markup(ns->nodes[i]);
|
|
1154
|
+
if (!out[i].str) {
|
|
1155
|
+
out[i].kind = 0;
|
|
1156
|
+
out[i].node = ns->nodes[i];
|
|
1157
|
+
}
|
|
1143
1158
|
} else {
|
|
1144
1159
|
out[i].kind = 0;
|
|
1145
1160
|
out[i].node = ns->nodes[i];
|
|
@@ -48,6 +48,8 @@ static int str_eq_coll(const char* a, const char* b,
|
|
|
48
48
|
extern struct leptris_xpath_result* xpath_evaluate(XPathContext* context,
|
|
49
49
|
XPathASTNode* ast);
|
|
50
50
|
extern char* get_node_text(void* node);
|
|
51
|
+
/* evaluator_operators.c — element members keep their markup (#181). */
|
|
52
|
+
extern char* ctor_member_markup(void* node);
|
|
51
53
|
|
|
52
54
|
/* ---- sequence plumbing ---- */
|
|
53
55
|
|
|
@@ -332,7 +334,11 @@ static char** collect_items_raw(XPathContext* ctx, XPathASTNode** args,
|
|
|
332
334
|
const char* c = ((XPathTextNode*)nd)->content;
|
|
333
335
|
items[cnt++] = leptris_strdup(c ? c : "");
|
|
334
336
|
} else {
|
|
335
|
-
|
|
337
|
+
/* Element members keep their MARKUP (#181: ctor
|
|
338
|
+
* nodes are real now) — a bare string-value fetch
|
|
339
|
+
* collapses every constructed element to its text
|
|
340
|
+
* (fn:reverse of (<a/>, <f/>) went empty). */
|
|
341
|
+
char* t = ctor_member_markup(nd);
|
|
336
342
|
items[cnt++] = t ? t : leptris_strdup("");
|
|
337
343
|
}
|
|
338
344
|
}
|
|
@@ -5583,16 +5589,26 @@ char* xpath_seq_carrier_of(struct leptris_xpath_result* vr) {
|
|
|
5583
5589
|
void* b);
|
|
5584
5590
|
b = xpath_map_builder_new();
|
|
5585
5591
|
if (!b) return NULL;
|
|
5586
|
-
for (size_t j = 0; j < mns->count; j++)
|
|
5587
|
-
|
|
5588
|
-
|
|
5592
|
+
for (size_t j = 0; j < mns->count; j++) {
|
|
5593
|
+
char* mc;
|
|
5594
|
+
int mtype = (int)XPATH_NODE_TYPE(mns->nodes[j]);
|
|
5595
|
+
if (mtype == (int)LEPTRIS_NODE_TEXT) {
|
|
5596
|
+
mc = leptris_strdup(
|
|
5597
|
+
((XPathTextNode*)mns->nodes[j])->content);
|
|
5598
|
+
} else if (mtype == (int)LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
5599
|
+
/* #181: materialized ctor members ride the carrier as
|
|
5600
|
+
* their serialized MARKUP (the pre-#181 member
|
|
5601
|
+
* spelling) so member COUNT survives typed argv;
|
|
5602
|
+
* pointer identity through the channel remains the
|
|
5603
|
+
* #181 follow-up. */
|
|
5604
|
+
mc = leptris_element_serialize(
|
|
5605
|
+
(LeptrisElement)mns->nodes[j], NULL);
|
|
5606
|
+
} else {
|
|
5589
5607
|
struct leptris_xpath_result* rel =
|
|
5590
5608
|
xpath_map_builder_finish(b);
|
|
5591
5609
|
if (rel) xpath_result_free(rel);
|
|
5592
5610
|
return NULL;
|
|
5593
5611
|
}
|
|
5594
|
-
for (size_t j = 0; j < mns->count; j++) {
|
|
5595
|
-
const char* mc = ((XPathTextNode*)mns->nodes[j])->content;
|
|
5596
5612
|
/* Member marks SURVIVE the carrier: the next call's FN
|
|
5597
5613
|
* binding needs them (declared-fold recursion re-collects
|
|
5598
5614
|
* typed/untyped items, cbcl-distinct-values-002). The FR
|
|
@@ -5600,6 +5616,7 @@ char* xpath_seq_carrier_of(struct leptris_xpath_result* vr) {
|
|
|
5600
5616
|
char key[24];
|
|
5601
5617
|
snprintf(key, sizeof(key), "%zu", j + 1);
|
|
5602
5618
|
xpath_map_builder_add(b, key, mc ? mc : "");
|
|
5619
|
+
free(mc);
|
|
5603
5620
|
}
|
|
5604
5621
|
struct leptris_xpath_result* subr = xpath_map_builder_finish(b);
|
|
5605
5622
|
char* out = NULL;
|
|
@@ -6687,8 +6704,8 @@ static struct leptris_xpath_result* fn_format_number_plain(
|
|
|
6687
6704
|
* result is consumed, so the ctx owned_docs chain cannot carry
|
|
6688
6705
|
* constructed nodes; the source doc's lifetime is the only owner
|
|
6689
6706
|
* that outlives the result. */
|
|
6690
|
-
|
|
6691
|
-
|
|
6707
|
+
int xq_anchor_on_source(XPathContext* ctx,
|
|
6708
|
+
struct leptris_document* anchored) {
|
|
6692
6709
|
struct leptris_document* src = ctx->document;
|
|
6693
6710
|
if (!src) {
|
|
6694
6711
|
leptris_document_free(anchored);
|
|
@@ -23,11 +23,20 @@ std::string seq_string(LeptrisDocument doc, const char* query) {
|
|
|
23
23
|
if (!r) { leptris_xquery_free(xq); return "(eval-failed)"; }
|
|
24
24
|
std::string out;
|
|
25
25
|
if (leptris_xpath_result_type(r) == LEPTRIS_XPATH_NODESET) {
|
|
26
|
-
/* FLWOR sequences join space-separated (value-of rule).
|
|
26
|
+
/* FLWOR sequences join space-separated (value-of rule).
|
|
27
|
+
* #181: materialized ctor ELEMENT members render as their
|
|
28
|
+
* serialized markup (the QT3 assert-xml contract); other
|
|
29
|
+
* members keep their string value. */
|
|
27
30
|
size_t n = leptris_xpath_result_count(r);
|
|
28
31
|
for (size_t i = 0; i < n; i++) {
|
|
29
|
-
const char* v = leptris_xpath_result_node_value(r, i);
|
|
30
32
|
if (i) out += ' ';
|
|
33
|
+
LeptrisElement el = leptris_xpath_result_get(r, i);
|
|
34
|
+
if (el) {
|
|
35
|
+
char* s = leptris_element_serialize(el, NULL);
|
|
36
|
+
if (s) { out += s; leptris_free_string(s); }
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const char* v = leptris_xpath_result_node_value(r, i);
|
|
31
40
|
out += v ? v : "";
|
|
32
41
|
}
|
|
33
42
|
} else {
|
|
@@ -42,6 +51,58 @@ std::string seq_string(LeptrisDocument doc, const char* query) {
|
|
|
42
51
|
|
|
43
52
|
} // namespace
|
|
44
53
|
|
|
54
|
+
// #181: element constructors yield REAL nodes — stable identity
|
|
55
|
+
// across references, fold callbacks, and axis steps (the
|
|
56
|
+
// fold-left-009/016 lever; ctors were serialized TEXT before).
|
|
57
|
+
TEST(XQueryNodeCtor, CtorSequenceHasStableIdentity) {
|
|
58
|
+
LeptrisDocument doc;
|
|
59
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
60
|
+
doc = leptris_parse_string(kBooks, strlen(kBooks), &st);
|
|
61
|
+
ASSERT_NE(doc, nullptr);
|
|
62
|
+
EXPECT_EQ(seq_string(doc,
|
|
63
|
+
"let $n := (<a/>, <b/>) "
|
|
64
|
+
"return count($n[. intersect $n[1]])"), "1");
|
|
65
|
+
leptris_document_free(doc);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* REMAINDER (task #181 follow-ups, in priority order):
|
|
69
|
+
* 1. LET-PREDICATE STABILITY: $nodes[1] re-evaluates the ctor per
|
|
70
|
+
* reference — each indexing is a NEW node, so identity-based
|
|
71
|
+
* dedup over let-bound ctor sequences is impossible until the
|
|
72
|
+
* let binding is evaluated once (var cache seam).
|
|
73
|
+
* 2. TYPED PARAMS: function($f as node()*, ...) closures take a
|
|
74
|
+
* string argv path that loses member identity (xq_fn_thunk).
|
|
75
|
+
* 3. DESCENDANT AXIS: $h//p from a var-bound ctor node misses
|
|
76
|
+
* (descendant-or-self over materialized nodes). */
|
|
77
|
+
|
|
78
|
+
/* KNOWN REMAINDER (task #181 follow-up): user-function arguments
|
|
79
|
+
* ride the string argv channel (xq_fn_thunk); a node sequence
|
|
80
|
+
* bound through `declare function local:d($s as node()*)`
|
|
81
|
+
* collapses. Pointer-carrying argv was probed and falsified
|
|
82
|
+
* (P-ref UAF); the channel needs a node-identity encoding. */
|
|
83
|
+
|
|
84
|
+
TEST(XQueryNodeCtor, CtorNodeTakesAxisSteps) {
|
|
85
|
+
LeptrisDocument doc;
|
|
86
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
87
|
+
doc = leptris_parse_string(kBooks, strlen(kBooks), &st);
|
|
88
|
+
ASSERT_NE(doc, nullptr);
|
|
89
|
+
EXPECT_EQ(seq_string(doc,
|
|
90
|
+
"count(<x><b/><b/></x>/b)"), "2");
|
|
91
|
+
leptris_document_free(doc);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
TEST(XQueryNodeCtor, DistinctCtorOccurrencesAreDistinctNodes) {
|
|
95
|
+
LeptrisDocument doc;
|
|
96
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
97
|
+
doc = leptris_parse_string(kBooks, strlen(kBooks), &st);
|
|
98
|
+
ASSERT_NE(doc, nullptr);
|
|
99
|
+
/* Two occurrences are two nodes; the same reference is itself. */
|
|
100
|
+
EXPECT_EQ(seq_string(doc,
|
|
101
|
+
"let $n := (<a/>, <b/>) "
|
|
102
|
+
"return ($n[1] is $n[1], $n[1] is $n[2])"), "true false");
|
|
103
|
+
leptris_document_free(doc);
|
|
104
|
+
}
|
|
105
|
+
|
|
45
106
|
TEST(XQueryCore, PlainExpressionIsXPath) {
|
|
46
107
|
LeptrisDocument doc = leptris_parse_string(kBooks, strlen(kBooks),
|
|
47
108
|
nullptr);
|
|
@@ -874,7 +935,7 @@ TEST(XQueryCore, DocumentConstructorWithDirectContent) {
|
|
|
874
935
|
* FLWOR bodies) keep their value form. */
|
|
875
936
|
EXPECT_EQ(seq_string(doc,
|
|
876
937
|
"string-join((string(document { <a/> }), <b>2</b>), '|')"),
|
|
877
|
-
"
|
|
938
|
+
"|2");
|
|
878
939
|
leptris_document_free(doc);
|
|
879
940
|
}
|
|
880
941
|
|
|
@@ -1407,8 +1468,12 @@ TEST(XQueryCtors, DirectPICommentCtors) {
|
|
|
1407
1468
|
{"deep-equal(<?foo?>, <?foo ?>)", "true"},
|
|
1408
1469
|
{"deep-equal(<!--x-->, <!--x-->)", "true"},
|
|
1409
1470
|
{"deep-equal(<!--x-->, <!--y-->)", "false"},
|
|
1410
|
-
|
|
1411
|
-
|
|
1471
|
+
/* result_string is the string-value contract (#514): no
|
|
1472
|
+
* text under <doc> renders "". The PI child surviving the
|
|
1473
|
+
* <doc> materialization is probed by the axis count. */
|
|
1474
|
+
{"<doc><?pi content?></doc>", ""},
|
|
1475
|
+
{"count(<doc><?pi content?></doc>/processing-instruction())",
|
|
1476
|
+
"1"},
|
|
1412
1477
|
};
|
|
1413
1478
|
for (auto& c : cases) {
|
|
1414
1479
|
LeptrisXQuery xq = leptris_xquery_parse(c.q, strlen(c.q));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.314",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|