leptris 1.9.313.0 → 1.9.317.0
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/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +92 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/bindings/rust/descriptor/src/lib.rs +6 -0
- data/vendor-src/libleptris/bindings/rust/descriptor/tests/layout.rs +1 -0
- data/vendor-src/libleptris/cli/commands/xquery.c +25 -2
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +8 -1
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +46 -5
- 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 +276 -24
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +35 -0
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +158 -28
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +48 -8
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +19 -0
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +16 -0
- data/vendor-src/libleptris/test/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +141 -0
- data/vendor-src/libleptris/test/parser/test_whitespace_shape.cpp +153 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +6 -6
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +134 -5
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44f00da545382eef1ba06a526478752ab7f2fb16ecc609a60df9fd88f1cd46aa
|
|
4
|
+
data.tar.gz: bcc75b40350199bdb1db2dd502d1d6fa1f0398f499bbda57e2aaf4bfe1c4901f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75b5e89df9de5a924bfdcce746668b6a2f81114d459de3ec9b8aae72c2acb11a6c1c26c1bd12b2a1227fc19d52873004badd77298ba9f4437877679a8dfa52c2
|
|
7
|
+
data.tar.gz: b1ed2f7fef77ba8900ab4a323cc1915ef501fd6cc11078b3912187349b7c62b13fb58766c623ad0db6f1a67ed71bcab19c9d5740785a951867769ea8b18d4852
|
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.317"
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.317] - 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Descriptor: unqualified-only ns form for child rows (#1560,
|
|
8
|
+
PR #1574).** `LEPTRIS_PLAN_NS_UNQUALIFIED = 3` matches the WRITTEN
|
|
9
|
+
spelling — a child row binds children with no written prefix
|
|
10
|
+
regardless of the effective namespace URI, and refuses prefixed
|
|
11
|
+
spellings. An unprefixed child binds under both a namespace-less
|
|
12
|
+
document and a default-`xmlns` document (`NS_NONE` drops the
|
|
13
|
+
latter); a `p:child` never binds (the `NS_ANY` superset
|
|
14
|
+
lutaml-model#932 had to settle for). The serialize -> re-parse ->
|
|
15
|
+
re-walk round-trip agrees on all three shapes because the form
|
|
16
|
+
keys on the spelling, not the URI: the serializer emits prefix
|
|
17
|
+
declarations only, so a default-ns child serializes bare and
|
|
18
|
+
still re-binds. Rust descriptor-ABI mirror shipped in the same
|
|
19
|
+
wave (leptris-rs#4).
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## [1.9.316] - 2026-10-07
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Descriptor plan serializer: nested captures emit their own rows
|
|
28
|
+
(#1565, PR #1573).** A captured child's rows now resolve against the
|
|
29
|
+
CAPTURING row's `child_plan_index` plan instead of the plan that
|
|
30
|
+
walked it, so a child bound by a nested row (the `w:font`-in-
|
|
31
|
+
`w:fonts` shape) serializes its own attribute and child rows —
|
|
32
|
+
previously they resolved against the parent plan and silently
|
|
33
|
+
vanished. CONTENT rows inline their text at every nesting level.
|
|
34
|
+
The new nested-capture spec also exposed a 2 x 16B leak: the inner
|
|
35
|
+
walk's ns stamp was overwritten without freeing; fixed
|
|
36
|
+
free-before-overwrite (leaks gate clean).
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## [1.9.315] - 2026-10-07
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- **Carrier-by-pointer node identity — QT3 fold-left-009/016
|
|
45
|
+
adopted (PR #1571).** Element members riding the higher-order
|
|
46
|
+
function argument channel keep their NODE POINTER (the \x03P
|
|
47
|
+
carrier), so node identity survives a string-typed channel:
|
|
48
|
+
`$foundSoFar intersect $this` dedups by node (fold-left-009's
|
|
49
|
+
distinct-nodes-stable), and fold results decode to real nodes so
|
|
50
|
+
`$result/text()` steps (fold-left-016's css-selector interpreter).
|
|
51
|
+
The fold suite runs 16/16 — both long-pinned cases adopted.
|
|
52
|
+
Pointers are eval-safe: constructor documents anchor on the
|
|
53
|
+
source tree (the #691 discipline). Along the way, three language
|
|
54
|
+
gaps closed: typeswitch arms accept `element(id)` /
|
|
55
|
+
`element(class)` NAME arguments with case-bound variables binding
|
|
56
|
+
node operands as nodes; keyword-named constructors parse
|
|
57
|
+
(`<div/>`, `<mod/>` — HTML tag names colliding with XQuery
|
|
58
|
+
operators); and single-element results ride the carrier instead
|
|
59
|
+
of collapsing to their string value.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Pointer carriers only for real ELEMENT members.** The item
|
|
64
|
+
collector handed \x03P carriers to synthetic attribute and
|
|
65
|
+
namespace nodes too — heap-owned by the carrying nodeset, so the
|
|
66
|
+
carrier dangled once it was freed (AddressSanitizer
|
|
67
|
+
heap-use-after-free via fn:subsequence over an attribute step).
|
|
68
|
+
Real elements stay document-anchored for the evaluation;
|
|
69
|
+
attributes and namespaces keep the markup snapshot.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## [1.9.314] - 2026-10-07
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **XQuery element constructors yield real nodes (#181, PR #1568).**
|
|
78
|
+
`<a>…</a>`, `element name {…}`, and PI/comment constructors
|
|
79
|
+
materialize into real documents and yield their root ELEMENT node
|
|
80
|
+
(the `document{}` pattern), so identity (`is`, `intersect`), axis
|
|
81
|
+
steps, and higher-order callbacks see one stable node per
|
|
82
|
+
evaluation. Constructed elements ride sequences, maps,
|
|
83
|
+
`fn:reverse`, `unordered`, and `deep-equal` as markup — the
|
|
84
|
+
string-value collapse that made every element look like its text
|
|
85
|
+
content is gone — and `leptris xquery` prints them serialized.
|
|
86
|
+
Materialized documents anchor on the source tree (the #691
|
|
87
|
+
discipline), fixing an order-dependent use-after-free when FLWOR
|
|
88
|
+
or window results outlive the evaluation context, and an
|
|
89
|
+
(AST, content)-keyed memo bounds per-reference re-materialization:
|
|
90
|
+
the fold-recursion stress peaks at ~83MB where the unbounded path
|
|
91
|
+
reached 200GB.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
3
95
|
## [1.9.313] - 2026-10-07
|
|
4
96
|
|
|
5
97
|
### Fixed
|
|
@@ -49,6 +49,12 @@ pub type PlanNsForm = u8;
|
|
|
49
49
|
pub const PLAN_NS_NONE: PlanNsForm = 0;
|
|
50
50
|
pub const PLAN_NS_EXACT: PlanNsForm = 1;
|
|
51
51
|
pub const PLAN_NS_ANY: PlanNsForm = 2;
|
|
52
|
+
/// #1560: matches the UNWRITTEN spelling — no written prefix,
|
|
53
|
+
/// regardless of the effective namespace URI. An unprefixed child
|
|
54
|
+
/// binds under both a namespace-less document and a default-xmlns
|
|
55
|
+
/// document; prefixed spellings never bind. Element rows only
|
|
56
|
+
/// (unprefixed attributes have no namespace by XML rules).
|
|
57
|
+
pub const PLAN_NS_UNQUALIFIED: PlanNsForm = 3;
|
|
52
58
|
|
|
53
59
|
/// Result node kinds (the value tree a walk returns).
|
|
54
60
|
pub type PlanValueKind = i32;
|
|
@@ -34,6 +34,7 @@ fn kind_and_flag_constants_match_engine_headers() {
|
|
|
34
34
|
assert_eq!(PLAN_NS_NONE, 0);
|
|
35
35
|
assert_eq!(PLAN_NS_EXACT, 1);
|
|
36
36
|
assert_eq!(PLAN_NS_ANY, 2);
|
|
37
|
+
assert_eq!(PLAN_NS_UNQUALIFIED, 3);
|
|
37
38
|
assert_eq!(PLAN_VALUE_ELEMENT, 0);
|
|
38
39
|
assert_eq!(PLAN_VALUE_SCALAR, 1);
|
|
39
40
|
assert_eq!(PLAN_VALUE_COLLECTION, 2);
|
|
@@ -119,11 +119,34 @@ static cli_result_t xquery_run(int argc, char** argv) {
|
|
|
119
119
|
cli_error("XQuery evaluation failed");
|
|
120
120
|
rc = CLI_ERROR_XPATH;
|
|
121
121
|
} else if (leptris_xpath_result_type(r) == LEPTRIS_XPATH_NODESET) {
|
|
122
|
+
/* carrier-by-pointer (fold-left-009/016): synth members may
|
|
123
|
+
* carry a node pointer — render its markup. */
|
|
124
|
+
extern void* xpath_carrier_node_of(const char* s);
|
|
122
125
|
size_t n = leptris_xpath_result_count(r);
|
|
123
126
|
for (size_t k = 0; k < n; k++) {
|
|
124
|
-
const char* v = leptris_xpath_result_node_value(r, k);
|
|
125
127
|
if (k) putchar(' ');
|
|
126
|
-
|
|
128
|
+
LeptrisElement el = NULL;
|
|
129
|
+
if (leptris_xpath_result_node_kind(r, k) ==
|
|
130
|
+
LEPTRIS_XPATH_NODE_ELEMENT) {
|
|
131
|
+
el = leptris_xpath_result_get(r, k);
|
|
132
|
+
} else {
|
|
133
|
+
const char* raw =
|
|
134
|
+
leptris_xpath_result_node_value(r, k);
|
|
135
|
+
if (raw && (unsigned char)raw[0] == 0x03 &&
|
|
136
|
+
raw[1] == 'P')
|
|
137
|
+
el = (LeptrisElement)xpath_carrier_node_of(raw);
|
|
138
|
+
}
|
|
139
|
+
/* Constructed elements (#181) are real nodes now — print
|
|
140
|
+
* their serialized markup, not the string-value (the
|
|
141
|
+
* pre-node ctor spelling printed markup too). */
|
|
142
|
+
if (el) {
|
|
143
|
+
char* m = leptris_element_serialize(el, NULL);
|
|
144
|
+
fputs(m ? m : "", stdout);
|
|
145
|
+
leptris_free_string(m);
|
|
146
|
+
} else {
|
|
147
|
+
const char* v = leptris_xpath_result_node_value(r, k);
|
|
148
|
+
fputs(v ? v : "", stdout);
|
|
149
|
+
}
|
|
127
150
|
}
|
|
128
151
|
if (n) putchar('\n');
|
|
129
152
|
} else {
|
|
@@ -96,7 +96,14 @@ typedef enum {
|
|
|
96
96
|
typedef enum {
|
|
97
97
|
LEPTRIS_PLAN_NS_NONE = 0, /* only elements with no namespace */
|
|
98
98
|
LEPTRIS_PLAN_NS_EXACT = 1, /* resolved namespace URI equals ns_uri */
|
|
99
|
-
LEPTRIS_PLAN_NS_ANY = 2
|
|
99
|
+
LEPTRIS_PLAN_NS_ANY = 2,
|
|
100
|
+
/* #1560: matches the UNWRITTEN spelling — children with no
|
|
101
|
+
* prefix, regardless of the effective namespace URI. An
|
|
102
|
+
* unprefixed child binds under both a namespace-less document
|
|
103
|
+
* and a default-xmlns document; prefixed spellings never bind.
|
|
104
|
+
* Unprefixed attributes have no namespace by XML rules, so
|
|
105
|
+
* this form only applies to element rows. */
|
|
106
|
+
LEPTRIS_PLAN_NS_UNQUALIFIED = 3
|
|
100
107
|
} LeptrisPlanNsForm;
|
|
101
108
|
|
|
102
109
|
typedef struct {
|
|
@@ -562,6 +562,11 @@ static int dp_ns_binds(LeptrisElement elem, uint8_t ns_form,
|
|
|
562
562
|
case LEPTRIS_PLAN_NS_ANY:
|
|
563
563
|
default:
|
|
564
564
|
return 1;
|
|
565
|
+
case LEPTRIS_PLAN_NS_UNQUALIFIED:
|
|
566
|
+
/* #1560: the WRITTEN spelling — no prefix, any
|
|
567
|
+
* effective URI (default-xmlns elements included);
|
|
568
|
+
* prefixed spellings never bind. */
|
|
569
|
+
return prefix == NULL;
|
|
565
570
|
}
|
|
566
571
|
}
|
|
567
572
|
|
|
@@ -852,6 +857,11 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
852
857
|
* back to the target plan's (NESTED matches with
|
|
853
858
|
* the target's form, #1115). */
|
|
854
859
|
if (row->ns_prefix) {
|
|
860
|
+
/* The inner walk's own #1551 stamp may already
|
|
861
|
+
* hold this pair — free before overwriting or
|
|
862
|
+
* the first strdup orphans. */
|
|
863
|
+
free(v->ns_prefix);
|
|
864
|
+
free(v->ns_uri);
|
|
855
865
|
v->ns_prefix = dp_strdup(row->ns_prefix);
|
|
856
866
|
v->ns_uri = dp_strdup(row->ns_uri ? row->ns_uri
|
|
857
867
|
: target->ns_uri);
|
|
@@ -1343,7 +1353,8 @@ static int dp_emit_value(const struct leptris_plan_result* v,
|
|
|
1343
1353
|
|
|
1344
1354
|
static int dp_emit_element(const struct leptris_plan_result* v,
|
|
1345
1355
|
const char* name, const LeptrisPlan pool,
|
|
1346
|
-
|
|
1356
|
+
const dp_plan* kids_container, dp_sb* b,
|
|
1357
|
+
const dp_decls* decls, int is_root) {
|
|
1347
1358
|
if (!dp_emit_element_open(v, name, b, decls, is_root)) return 0;
|
|
1348
1359
|
const struct leptris_plan_result** kids = NULL;
|
|
1349
1360
|
size_t nk = 0, cap = 0;
|
|
@@ -1360,11 +1371,14 @@ static int dp_emit_element(const struct leptris_plan_result* v,
|
|
|
1360
1371
|
return 0;
|
|
1361
1372
|
}
|
|
1362
1373
|
qsort((void*)kids, nk, sizeof(*kids), dp_kid_pos_cmp);
|
|
1363
|
-
|
|
1364
|
-
|
|
1374
|
+
/* #1565: kid ROWS live in the plan that WALKED this element —
|
|
1375
|
+
* container_plan is the plan whose row CAPTURED it (the
|
|
1376
|
+
* parent), so a nested member resolved its kids' rows against
|
|
1377
|
+
* the parent's rows and a CONTENT row serialized as an
|
|
1378
|
+
* empty-named wrapper. */
|
|
1365
1379
|
int ok = 1;
|
|
1366
1380
|
for (size_t i = 0; i < nk && ok; i++)
|
|
1367
|
-
ok = dp_emit_value(kids[i],
|
|
1381
|
+
ok = dp_emit_value(kids[i], kids_container, pool, b, NULL, 0);
|
|
1368
1382
|
free((void*)kids);
|
|
1369
1383
|
if (!ok) return 0;
|
|
1370
1384
|
return dp_sb_puts(b, "</") && dp_sb_puts(b, name) &&
|
|
@@ -1377,6 +1391,16 @@ static int dp_emit_value(const struct leptris_plan_result* v,
|
|
|
1377
1391
|
if (v->kind == LEPTRIS_PLAN_VALUE_RAW)
|
|
1378
1392
|
return dp_sb_puts(b, v->str ? v->str : "");
|
|
1379
1393
|
if (v->kind == LEPTRIS_PLAN_VALUE_ELEMENT) {
|
|
1394
|
+
/* #1565: a walk member captured by a CONTENT row is an
|
|
1395
|
+
* unnamed element — the row lookup must precede the name
|
|
1396
|
+
* resolution, or the empty wire name becomes the wrapper
|
|
1397
|
+
* (<w:item><>text</></w:item>). Content emits inline text
|
|
1398
|
+
* at every nesting level. */
|
|
1399
|
+
if (container && v->row_index != UINT32_MAX &&
|
|
1400
|
+
v->row_index < container->child_count &&
|
|
1401
|
+
container->child_plans[v->row_index].kind ==
|
|
1402
|
+
LEPTRIS_PLAN_KIND_CONTENT)
|
|
1403
|
+
return dp_sb_escape(b, v->str ? v->str : "", 0);
|
|
1380
1404
|
/* The element's own binding was stamped at walk time; a
|
|
1381
1405
|
* walk member without one falls back to its plan's. */
|
|
1382
1406
|
char* name = NULL;
|
|
@@ -1397,7 +1421,24 @@ static int dp_emit_value(const struct leptris_plan_result* v,
|
|
|
1397
1421
|
else
|
|
1398
1422
|
name = dp_strdup(local ? local : "");
|
|
1399
1423
|
if (!name) return 0;
|
|
1400
|
-
|
|
1424
|
+
/* Kids resolve their rows against the plan that WALKED this
|
|
1425
|
+
* element: the capturing row's child_plan_index when nested,
|
|
1426
|
+
* else container_plan (the walk root is its own plan). */
|
|
1427
|
+
const struct leptris_plan* poolp =
|
|
1428
|
+
(const struct leptris_plan*)pool;
|
|
1429
|
+
const dp_plan* kids_container =
|
|
1430
|
+
&poolp->plans[v->container_plan];
|
|
1431
|
+
if (container && v->row_index != UINT32_MAX &&
|
|
1432
|
+
v->row_index < container->child_count) {
|
|
1433
|
+
const leptris_child_plan* cap =
|
|
1434
|
+
&container->child_plans[v->row_index];
|
|
1435
|
+
if (cap->child_plan_index >= 0 &&
|
|
1436
|
+
(uint32_t)cap->child_plan_index < poolp->plan_count)
|
|
1437
|
+
kids_container =
|
|
1438
|
+
&poolp->plans[cap->child_plan_index];
|
|
1439
|
+
}
|
|
1440
|
+
int ok = dp_emit_element(v, name, pool, kids_container, b,
|
|
1441
|
+
decls, is_root);
|
|
1401
1442
|
free(name);
|
|
1402
1443
|
return ok;
|
|
1403
1444
|
}
|
|
@@ -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) {
|