leptris 1.9.231.1-arm-linux → 1.9.232.1-arm-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 +19 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +35 -4
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +88 -62
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/node.rb +28 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +15 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +5 -1
- data/vendor-src/libleptris/src/include/leptris.h +17 -0
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +26 -6
- data/vendor-src/libleptris/src/leptris/dom/node.c +24 -7
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +22 -8
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +29 -8
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +30 -1
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +106 -6
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +68 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +56 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/codepoints-to-string.xml +808 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/deep-equal.xml +2709 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/distinct-values.xml +1223 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/index-of.xml +493 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/insert-before.xml +399 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/remove.xml +480 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/reverse.xml +653 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/string-to-codepoints.xml +452 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/subsequence.xml +1041 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +179 -1
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +27 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +11 -2
|
@@ -440,6 +440,29 @@ LEPTRIS_API void leptris_plan_free(LeptrisPlan plan) { dp_plan_free(plan); }
|
|
|
440
440
|
/* ---- walk ------------------------------------------------------- */
|
|
441
441
|
|
|
442
442
|
/* Namespace binding for a child element against a row's target form. */
|
|
443
|
+
/* Child-row wire-name match. The ABI documents wire_name as "the
|
|
444
|
+
* XML element name as it appears on the wire": a row may carry the
|
|
445
|
+
* PREFIXED form ("w:b") or the bare local name ("b"). Rows with a
|
|
446
|
+
* colon bind prefix+local (literal prefix, per the wire contract);
|
|
447
|
+
* bare rows keep the historical local-name match with the row's
|
|
448
|
+
* ns_form doing the namespace work. The old matcher compared the
|
|
449
|
+
* local name only, so every prefixed row — the shape hosts
|
|
450
|
+
* compiling from xsd:QName schemas emit — silently bound nothing
|
|
451
|
+
* and whole subtrees hydrated empty (XSD-002 fallout). */
|
|
452
|
+
static int dp_wire_name_matches(LeptrisElement child,
|
|
453
|
+
const char* wire_name) {
|
|
454
|
+
const char* local = NULL;
|
|
455
|
+
const char* prefix = NULL;
|
|
456
|
+
leptris_element_expanded_name(child, &local, &prefix, NULL);
|
|
457
|
+
if (!wire_name || !local) return 0;
|
|
458
|
+
const char* colon = strchr(wire_name, ':');
|
|
459
|
+
if (!colon) return strcmp(local, wire_name) == 0;
|
|
460
|
+
size_t plen = (size_t)(colon - wire_name);
|
|
461
|
+
return prefix != NULL && strlen(prefix) == plen &&
|
|
462
|
+
strncmp(prefix, wire_name, plen) == 0 &&
|
|
463
|
+
strcmp(local, colon + 1) == 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
443
466
|
static int dp_ns_binds(LeptrisElement elem, uint8_t ns_form,
|
|
444
467
|
const char* ns_uri, int lenient) {
|
|
445
468
|
const char* local = NULL;
|
|
@@ -617,9 +640,8 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
617
640
|
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
618
641
|
continue;
|
|
619
642
|
LeptrisElement child = (LeptrisElement)n;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
if (!local || strcmp(local, row->wire_name) != 0) continue;
|
|
643
|
+
if (!dp_wire_name_matches(child, row->wire_name))
|
|
644
|
+
continue;
|
|
623
645
|
if (!dp_ns_binds(child, target->ns_form, target->ns_uri,
|
|
624
646
|
lenient))
|
|
625
647
|
continue;
|
|
@@ -666,9 +688,7 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
|
|
|
666
688
|
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
667
689
|
continue;
|
|
668
690
|
LeptrisElement child = (LeptrisElement)n;
|
|
669
|
-
|
|
670
|
-
leptris_element_expanded_name(child, &local, NULL, NULL);
|
|
671
|
-
if (!local || strcmp(local, row->wire_name) != 0) continue;
|
|
691
|
+
if (!dp_wire_name_matches(child, row->wire_name)) continue;
|
|
672
692
|
/* #1272: same-wire-name claim — if a previous row bound
|
|
673
693
|
* this node, skip it. */
|
|
674
694
|
int rank = -1;
|
|
@@ -389,18 +389,20 @@ void leptris_node_set_next_sibling(LeptrisNode* node, LeptrisNode* sibling) {
|
|
|
389
389
|
* lazily from the callback instead of materializing per-level
|
|
390
390
|
* NodeSet/Array allocations. */
|
|
391
391
|
static void node_visit_rec(LeptrisNode* n, int depth,
|
|
392
|
-
LeptrisNodeVisitor visitor, void* user_data
|
|
392
|
+
LeptrisNodeVisitor visitor, void* user_data,
|
|
393
|
+
int entering_only) {
|
|
393
394
|
int is_elem = (n->type == LEPTRIS_NODE_TYPE_ELEMENT);
|
|
394
395
|
visitor(user_data, (LeptrisNodeRef)n, 1, depth);
|
|
395
396
|
if (!is_elem) return;
|
|
396
397
|
for (LeptrisNode* c = leptris_elem_first_child((LeptrisElement)n); c;
|
|
397
398
|
c = leptris_node_get_next_sibling(c))
|
|
398
|
-
node_visit_rec(c, depth + 1, visitor, user_data);
|
|
399
|
-
|
|
399
|
+
node_visit_rec(c, depth + 1, visitor, user_data, entering_only);
|
|
400
|
+
if (!entering_only)
|
|
401
|
+
visitor(user_data, (LeptrisNodeRef)n, 0, depth);
|
|
400
402
|
}
|
|
401
403
|
|
|
402
|
-
void
|
|
403
|
-
|
|
404
|
+
static void node_visit_impl(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
405
|
+
void* user_data, int entering_only) {
|
|
404
406
|
if (!root || !visitor) return;
|
|
405
407
|
if (root->type == LEPTRIS_NODE_TYPE_DOCUMENT) {
|
|
406
408
|
/* Document root: children = the document child chain (#580),
|
|
@@ -413,8 +415,23 @@ void leptris_node_visit(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
|
413
415
|
if (!start) start = (LeptrisNode*)d->root;
|
|
414
416
|
for (LeptrisNode* c = start; c;
|
|
415
417
|
c = leptris_node_get_next_sibling(c))
|
|
416
|
-
node_visit_rec(c, 0, visitor, user_data);
|
|
418
|
+
node_visit_rec(c, 0, visitor, user_data, entering_only);
|
|
417
419
|
return;
|
|
418
420
|
}
|
|
419
|
-
node_visit_rec((LeptrisNode*)root, 0, visitor, user_data
|
|
421
|
+
node_visit_rec((LeptrisNode*)root, 0, visitor, user_data,
|
|
422
|
+
entering_only);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
void leptris_node_visit(LeptrisNodeRef root, LeptrisNodeVisitor visitor,
|
|
426
|
+
void* user_data) {
|
|
427
|
+
node_visit_impl(root, visitor, user_data, 0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Issue #1332: entering-only mode — one callback per node. The
|
|
431
|
+
* family-walk consumers all discard the leaving half of
|
|
432
|
+
* leptris_node_visit; this skips those invocations at the source. */
|
|
433
|
+
void leptris_node_visit_entering(LeptrisNodeRef root,
|
|
434
|
+
LeptrisNodeVisitor visitor,
|
|
435
|
+
void* user_data) {
|
|
436
|
+
node_visit_impl(root, visitor, user_data, 1);
|
|
420
437
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <string.h>
|
|
13
13
|
#include <stdio.h>
|
|
14
14
|
#include <stdlib.h>
|
|
15
|
+
#include <limits.h>
|
|
15
16
|
/* ---- ISO 8601 dayTimeDuration value model (op:duration batch,
|
|
16
17
|
* lever 6 stage-2). Durations ride the STRING lexicon; these
|
|
17
18
|
* helpers give arithmetic and comparisons a typed view. Strictly
|
|
@@ -2037,8 +2038,8 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2037
2038
|
struct leptris_xpath_result* hi_r =
|
|
2038
2039
|
evaluate_expr(ctx, ast->children[1]);
|
|
2039
2040
|
if (!hi_r) { xpath_result_free(lo_r); return NULL; }
|
|
2040
|
-
|
|
2041
|
-
|
|
2041
|
+
double lod = xpath_to_number(lo_r);
|
|
2042
|
+
double hid = xpath_to_number(hi_r);
|
|
2042
2043
|
xpath_result_free(lo_r);
|
|
2043
2044
|
xpath_result_free(hi_r);
|
|
2044
2045
|
|
|
@@ -2046,12 +2047,25 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2046
2047
|
if (!out) return NULL;
|
|
2047
2048
|
out->owns_synthetic_text = 1;
|
|
2048
2049
|
out->is_sequence = 1;
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2050
|
+
/* XPath 2.0+: an empty (NaN) operand yields the empty
|
|
2051
|
+
* sequence — never cast NaN to long (UB: arm64 gives 0,
|
|
2052
|
+
* x86 gives INT32/64_MIN, so the "range" silently became
|
|
2053
|
+
* a 75/100000-item sequence; cbcl-codepoints-to-string-020).
|
|
2054
|
+
* Huge finite values clamp instead of UB-casting. */
|
|
2055
|
+
if (!isnan(lod) && !isnan(hid)) {
|
|
2056
|
+
long lo = lod > 9.0e18 ? LONG_MAX
|
|
2057
|
+
: lod < -9.0e18 ? LONG_MIN
|
|
2058
|
+
: (long)lod;
|
|
2059
|
+
long hi = hid > 9.0e18 ? LONG_MAX
|
|
2060
|
+
: hid < -9.0e18 ? LONG_MIN
|
|
2061
|
+
: (long)hid;
|
|
2062
|
+
for (long v = lo; v <= hi && v - lo < 100000; v++) {
|
|
2063
|
+
char buf[28];
|
|
2064
|
+
int l = snprintf(buf, sizeof buf, "\x03N%ld", v);
|
|
2065
|
+
XPathTextNode* tn = synth_text(buf, (size_t)l);
|
|
2066
|
+
if (!tn) break;
|
|
2067
|
+
xpath_nodeset_add(out, tn);
|
|
2068
|
+
}
|
|
2055
2069
|
}
|
|
2056
2070
|
struct leptris_xpath_result* result =
|
|
2057
2071
|
xpath_result_new(XPATH_RESULT_NODESET);
|
|
@@ -763,7 +763,12 @@ static int de_item_equal(DeItem* a, DeItem* b) {
|
|
|
763
763
|
if (a->kind != b->kind) return 0;
|
|
764
764
|
switch (a->kind) {
|
|
765
765
|
case 0: return de_node_equal(a->node, b->node);
|
|
766
|
-
case 1:
|
|
766
|
+
case 1: {
|
|
767
|
+
/* deep-equal is atomic-eq based: NaN compares equal to
|
|
768
|
+
* NaN (F&O 17.4.1 — unlike the eq operator). */
|
|
769
|
+
if (isnan(a->num) && isnan(b->num)) return 1;
|
|
770
|
+
return a->num == b->num;
|
|
771
|
+
}
|
|
767
772
|
case 2: {
|
|
768
773
|
const char* as = a->str ? a->str : (a->borrow ? a->borrow : "");
|
|
769
774
|
const char* bs = b->str ? b->str : (b->borrow ? b->borrow : "");
|
|
@@ -843,17 +848,33 @@ static struct leptris_xpath_result* xpath_func_deep_equal(XPathContext* context,
|
|
|
843
848
|
return NULL;
|
|
844
849
|
}
|
|
845
850
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
size_t
|
|
851
|
+
/* Growable item arrays: corpus sequences run to tens of
|
|
852
|
+
* thousands of members (cbcl roundtrips over 65536 to 100000);
|
|
853
|
+
* a fixed cap silently compares a prefix. */
|
|
854
|
+
size_t capa = a->type == XPATH_RESULT_NODESET && a->value.nodeset_value
|
|
855
|
+
? a->value.nodeset_value->count : 1;
|
|
856
|
+
size_t capb = b->type == XPATH_RESULT_NODESET && b->value.nodeset_value
|
|
857
|
+
? b->value.nodeset_value->count : 1;
|
|
858
|
+
DeItem* ia = (DeItem*)malloc((capa ? capa : 1) * sizeof(DeItem));
|
|
859
|
+
DeItem* ib = (DeItem*)malloc((capb ? capb : 1) * sizeof(DeItem));
|
|
860
|
+
if (!ia || !ib) {
|
|
861
|
+
free(ia);
|
|
862
|
+
free(ib);
|
|
863
|
+
xpath_result_free(a);
|
|
864
|
+
xpath_result_free(b);
|
|
865
|
+
return NULL;
|
|
866
|
+
}
|
|
867
|
+
size_t na = de_collect(a, ia, capa);
|
|
868
|
+
size_t nb = de_collect(b, ib, capb);
|
|
850
869
|
|
|
851
870
|
int equal = na == nb;
|
|
852
|
-
for (size_t i = 0; equal && i < na
|
|
871
|
+
for (size_t i = 0; equal && i < na; i++)
|
|
853
872
|
equal = de_item_equal(&ia[i], &ib[i]);
|
|
854
873
|
|
|
855
|
-
for (size_t i = 0; i < na
|
|
856
|
-
for (size_t i = 0; i < nb
|
|
874
|
+
for (size_t i = 0; i < na; i++) de_item_clear(&ia[i]);
|
|
875
|
+
for (size_t i = 0; i < nb; i++) de_item_clear(&ib[i]);
|
|
876
|
+
free(ia);
|
|
877
|
+
free(ib);
|
|
857
878
|
xpath_result_free(a);
|
|
858
879
|
xpath_result_free(b);
|
|
859
880
|
|
|
@@ -59,7 +59,20 @@ static void seq_push_str(struct leptris_xpath_result* seq, const char* s) {
|
|
|
59
59
|
|
|
60
60
|
static void seq_push_num(struct leptris_xpath_result* seq, double d) {
|
|
61
61
|
char* s = xpath_number_to_string(d);
|
|
62
|
-
|
|
62
|
+
if (!s) return;
|
|
63
|
+
/* "\x03N" marks numeric members — same discipline as the
|
|
64
|
+
* sequence constructor: per-member type checks (deep-equal,
|
|
65
|
+
* instance of) must see a NUMBER, not a string that happens
|
|
66
|
+
* to look like one. String consumers strip the marker. */
|
|
67
|
+
size_t sl = strlen(s);
|
|
68
|
+
char* marked = (char*)malloc(sl + 3);
|
|
69
|
+
if (marked) {
|
|
70
|
+
marked[0] = '\x03';
|
|
71
|
+
marked[1] = 'N';
|
|
72
|
+
memcpy(marked + 2, s, sl + 1);
|
|
73
|
+
seq_push_str(seq, marked);
|
|
74
|
+
free(marked);
|
|
75
|
+
}
|
|
63
76
|
free(s);
|
|
64
77
|
}
|
|
65
78
|
|
|
@@ -71,6 +84,11 @@ static char* scalar_str(const struct leptris_xpath_result* r) {
|
|
|
71
84
|
return leptris_strdup(r->value.string_value
|
|
72
85
|
? r->value.string_value : "");
|
|
73
86
|
case XPATH_RESULT_NUMBER: {
|
|
87
|
+
/* int64 fidelity: an xs:integer-sourced number must
|
|
88
|
+
* string exactly — the double roundtrip prints
|
|
89
|
+
* 830993497117024304 as 8.30993e+17. */
|
|
90
|
+
if (r->is_int)
|
|
91
|
+
return xpath_int_to_string(r->int_value);
|
|
74
92
|
char* s = xpath_number_to_string(r->value.number_value);
|
|
75
93
|
return s ? s : leptris_strdup("");
|
|
76
94
|
}
|
|
@@ -222,6 +240,13 @@ static struct leptris_xpath_result* fn_subsequence(XPathContext* ctx,
|
|
|
222
240
|
if (sv) leptris_xpath_result_free(sv);
|
|
223
241
|
struct leptris_xpath_result* out = seq_new();
|
|
224
242
|
if (out) {
|
|
243
|
+
/* F&O: a negative $length yields the empty sequence
|
|
244
|
+
* (K-SeqSubsequenceFunc-5) — len < 0 is NOT the "to end"
|
|
245
|
+
* sentinel here. */
|
|
246
|
+
if (n >= 3 && ld < 0) {
|
|
247
|
+
free_items(items, cnt);
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
225
250
|
long start = (long)sd;
|
|
226
251
|
long len = (n >= 3) ? (ld != ld ? -1 : (long)ld) : -1;
|
|
227
252
|
seq_from_items(out, items, cnt, start, len);
|
|
@@ -257,6 +282,10 @@ static struct leptris_xpath_result* fn_insert_before(XPathContext* ctx,
|
|
|
257
282
|
size_t icnt;
|
|
258
283
|
char** ins = collect_items(ctx, args, n, 2, &icnt);
|
|
259
284
|
struct leptris_xpath_result* out = seq_new();
|
|
285
|
+
/* F&O: position < 1 inserts at 1; a position beyond the tail
|
|
286
|
+
* inserts at the end (K-SeqInsertBeforeFunc-5/10/14/15). */
|
|
287
|
+
if (at < 1) at = 1;
|
|
288
|
+
if (at > (long)cnt + 1) at = (long)cnt + 1;
|
|
260
289
|
if (out && ins) {
|
|
261
290
|
for (long k = 1; k <= (long)(cnt + icnt); k++) {
|
|
262
291
|
if (k == at)
|
|
@@ -2382,6 +2382,98 @@ static XPathASTNode* parse_switch_expr(XPathParser* parser) {
|
|
|
2382
2382
|
}
|
|
2383
2383
|
}
|
|
2384
2384
|
}
|
|
2385
|
+
|
|
2386
|
+
/* String literal body -> decoded value (malloc'd). XQuery 3.1
|
|
2387
|
+
* 3.1.1: character references (
, A, 😀) expand to
|
|
2388
|
+
* the referenced codepoint -- EXEMPT from line-end normalization
|
|
2389
|
+
* (QT3 K-CodepointToStringFunc-13) -- and the five predefined
|
|
2390
|
+
* entity refs expand likewise. A '&' that is not a well-formed
|
|
2391
|
+
* reference stays literal (the text-path leniency). Codepoints are
|
|
2392
|
+
* UTF-8 encoded, so astral refs work. */
|
|
2393
|
+
static char* decode_string_literal(const char* p, size_t n) {
|
|
2394
|
+
char* out = (char*)malloc(n + 1);
|
|
2395
|
+
if (!out) return NULL;
|
|
2396
|
+
size_t o = 0;
|
|
2397
|
+
const char* pe = p + n;
|
|
2398
|
+
while (p < pe) {
|
|
2399
|
+
if (*p != '&') {
|
|
2400
|
+
out[o++] = *p++;
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
const char* semi = memchr(p, ';', (size_t)(pe - p));
|
|
2404
|
+
static const char* const k_predef[] = { "amp", "lt", "gt",
|
|
2405
|
+
"quot", "apos" };
|
|
2406
|
+
static const char k_predef_ch[] = { '&', '<', '>', '"', '\'' };
|
|
2407
|
+
int handled = 0;
|
|
2408
|
+
if (semi && (size_t)(semi - p) <= 12) {
|
|
2409
|
+
size_t rl = (size_t)(semi - p - 1);
|
|
2410
|
+
const char* r = p + 1;
|
|
2411
|
+
if (*r == '#') {
|
|
2412
|
+
r++;
|
|
2413
|
+
int hex = 0;
|
|
2414
|
+
if (r < semi && (*r == 'x' || *r == 'X')) {
|
|
2415
|
+
hex = 1;
|
|
2416
|
+
r++;
|
|
2417
|
+
}
|
|
2418
|
+
unsigned long cp = 0;
|
|
2419
|
+
const char* ds = r;
|
|
2420
|
+
while (r < semi) {
|
|
2421
|
+
char c = *r;
|
|
2422
|
+
int dv = hex ? (c >= '0' && c <= '9')
|
|
2423
|
+
? c - '0'
|
|
2424
|
+
: (c >= 'a' && c <= 'f')
|
|
2425
|
+
? c - 'a' + 10
|
|
2426
|
+
: (c >= 'A' && c <= 'F')
|
|
2427
|
+
? c - 'A' + 10
|
|
2428
|
+
: -1
|
|
2429
|
+
: (c >= '0' && c <= '9') ? c - '0' : -1;
|
|
2430
|
+
if (dv < 0) break;
|
|
2431
|
+
cp = cp * (hex ? 16ul : 10ul) + (unsigned long)dv;
|
|
2432
|
+
if (cp > 0x110000ul) cp = 0x110001ul;
|
|
2433
|
+
r++;
|
|
2434
|
+
}
|
|
2435
|
+
if (r == semi && r > ds &&
|
|
2436
|
+
(cp == 0x9ul || cp == 0xAul || cp == 0xDul ||
|
|
2437
|
+
(cp >= 0x20ul && cp <= 0x10FFFFul)) &&
|
|
2438
|
+
(cp < 0xD800ul || cp > 0xDFFFul)) {
|
|
2439
|
+
if (cp < 0x80ul) {
|
|
2440
|
+
out[o++] = (char)cp;
|
|
2441
|
+
} else if (cp < 0x800ul) {
|
|
2442
|
+
out[o++] = (char)(0xC0 | (cp >> 6));
|
|
2443
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
2444
|
+
} else if (cp < 0x10000ul) {
|
|
2445
|
+
out[o++] = (char)(0xE0 | (cp >> 12));
|
|
2446
|
+
out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
2447
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
2448
|
+
} else {
|
|
2449
|
+
out[o++] = (char)(0xF0 | (cp >> 18));
|
|
2450
|
+
out[o++] = (char)(0x80 | ((cp >> 12) & 0x3F));
|
|
2451
|
+
out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
|
|
2452
|
+
out[o++] = (char)(0x80 | (cp & 0x3F));
|
|
2453
|
+
}
|
|
2454
|
+
handled = 1;
|
|
2455
|
+
}
|
|
2456
|
+
} else {
|
|
2457
|
+
for (int i = 0; i < 5; i++) {
|
|
2458
|
+
if (rl == strlen(k_predef[i]) &&
|
|
2459
|
+
memcmp(r, k_predef[i], rl) == 0) {
|
|
2460
|
+
out[o++] = k_predef_ch[i];
|
|
2461
|
+
handled = 1;
|
|
2462
|
+
break;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
if (handled) {
|
|
2468
|
+
p = semi + 1;
|
|
2469
|
+
} else {
|
|
2470
|
+
out[o++] = *p++;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
out[o] = '\0';
|
|
2474
|
+
return out;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2385
2477
|
static XPathASTNode* parse_primary_expr(XPathParser* parser) {
|
|
2386
2478
|
XPathToken* tok = current_token(parser);
|
|
2387
2479
|
if (!tok) {
|
|
@@ -2423,13 +2515,21 @@ static XPathASTNode* parse_primary_expr(XPathParser* parser) {
|
|
|
2423
2515
|
XPathASTNode* node = ast_node_new(XPATH_AST_STRING);
|
|
2424
2516
|
if (!node) return NULL;
|
|
2425
2517
|
|
|
2426
|
-
/* Remove quotes
|
|
2518
|
+
/* Remove quotes; expand references when present. The scan
|
|
2519
|
+
* for '&' keeps the plain-literal path a memcpy (the common
|
|
2520
|
+
* case pays nothing). */
|
|
2427
2521
|
if (tok->value_len >= 2) {
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
if (
|
|
2431
|
-
|
|
2432
|
-
|
|
2522
|
+
const char* bp = tok->value + 1;
|
|
2523
|
+
size_t blen = tok->value_len - 2;
|
|
2524
|
+
if (blen && memchr(bp, '&', blen)) {
|
|
2525
|
+
node->value = decode_string_literal(bp, blen);
|
|
2526
|
+
}
|
|
2527
|
+
if (!node->value) {
|
|
2528
|
+
node->value = LEPTRIS_ALLOC_N(char, blen + 1);
|
|
2529
|
+
if (node->value) {
|
|
2530
|
+
memcpy(node->value, bp, blen);
|
|
2531
|
+
node->value[blen] = '\0';
|
|
2532
|
+
}
|
|
2433
2533
|
}
|
|
2434
2534
|
}
|
|
2435
2535
|
advance_token(parser);
|
|
@@ -386,6 +386,74 @@ TEST(PlanWalk, NamespaceFormsAndLeniency) {
|
|
|
386
386
|
leptris_document_free(doc);
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
/* XSD-002 fallout class: child rows whose wire_name is the PREFIXED
|
|
390
|
+
* form ("w:b") — exactly what the ABI doc promises ("XML element
|
|
391
|
+
* name as it appears on the wire") — must bind namespaced elements.
|
|
392
|
+
* The matcher historically compared the LOCAL name only, so every
|
|
393
|
+
* prefixed row silently matched nothing: hosts hydrated empty
|
|
394
|
+
* subtrees (attributes read NULL, elements fell back to defaults)
|
|
395
|
+
* and re-serialized without them. Local-name rows keep binding. */
|
|
396
|
+
TEST(PlanWalk, PrefixedWireNamesBindNamespacedElements) {
|
|
397
|
+
LeptrisDocument doc = parse(
|
|
398
|
+
"<w:document xmlns:w=\"urn:w\">"
|
|
399
|
+
"<w:r><w:b w:val=\"false\"/></w:r>"
|
|
400
|
+
"</w:document>");
|
|
401
|
+
ASSERT_NE(doc, nullptr);
|
|
402
|
+
LeptrisElement root = leptris_document_root(doc);
|
|
403
|
+
|
|
404
|
+
leptris_attr_plan b_attrs[] = {
|
|
405
|
+
{"w:val", LEPTRIS_PLAN_KIND_SCALAR, 7, 0, 0, nullptr},
|
|
406
|
+
};
|
|
407
|
+
leptris_child_plan b_row = {};
|
|
408
|
+
b_row.wire_name = "w:b";
|
|
409
|
+
b_row.kind = LEPTRIS_PLAN_KIND_NESTED;
|
|
410
|
+
b_row.child_plan_index = 2;
|
|
411
|
+
b_row.ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
412
|
+
leptris_child_plan r_row = {};
|
|
413
|
+
r_row.wire_name = "w:r";
|
|
414
|
+
r_row.kind = LEPTRIS_PLAN_KIND_NESTED;
|
|
415
|
+
r_row.child_plan_index = 1;
|
|
416
|
+
r_row.ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
417
|
+
leptris_element_plan plans[3] = {};
|
|
418
|
+
plans[0].element_name = "document";
|
|
419
|
+
plans[0].ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
420
|
+
plans[0].child_count = 1;
|
|
421
|
+
plans[0].child_plans = &r_row;
|
|
422
|
+
plans[1].element_name = "r";
|
|
423
|
+
plans[1].ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
424
|
+
plans[1].child_count = 1;
|
|
425
|
+
plans[1].child_plans = &b_row;
|
|
426
|
+
plans[2].element_name = "b";
|
|
427
|
+
plans[2].ns_form = LEPTRIS_PLAN_NS_ANY;
|
|
428
|
+
plans[2].attribute_count = 1;
|
|
429
|
+
plans[2].attribute_plans = b_attrs;
|
|
430
|
+
|
|
431
|
+
leptris_plan_spec spec = {};
|
|
432
|
+
spec.abi_version = leptris_plan_abi_version();
|
|
433
|
+
spec.plan_count = 3;
|
|
434
|
+
spec.plans = plans;
|
|
435
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
436
|
+
LeptrisPlan plan = leptris_plan_build(&spec, &st);
|
|
437
|
+
ASSERT_NE(plan, nullptr);
|
|
438
|
+
LeptrisPlanResult r = leptris_plan_walk(doc, root, plan, &st);
|
|
439
|
+
ASSERT_NE(r, nullptr);
|
|
440
|
+
ASSERT_EQ(leptris_plan_value_count(r), 1u);
|
|
441
|
+
LeptrisPlanResult rcoll = leptris_plan_value_at(r, 0);
|
|
442
|
+
ASSERT_NE(rcoll, nullptr);
|
|
443
|
+
/* the nested w:r row binds its element value */
|
|
444
|
+
ASSERT_EQ(leptris_plan_value_kind(rcoll), LEPTRIS_PLAN_VALUE_ELEMENT);
|
|
445
|
+
ASSERT_EQ(leptris_plan_value_count(rcoll), 1u);
|
|
446
|
+
LeptrisPlanResult bval = leptris_plan_value_at(rcoll, 0);
|
|
447
|
+
ASSERT_NE(bval, nullptr);
|
|
448
|
+
ASSERT_EQ(leptris_plan_value_kind(bval), LEPTRIS_PLAN_VALUE_ELEMENT);
|
|
449
|
+
const char* val = leptris_plan_value_attribute(bval, "w:val");
|
|
450
|
+
ASSERT_NE(val, nullptr);
|
|
451
|
+
EXPECT_STREQ(val, "false");
|
|
452
|
+
leptris_plan_result_free(r);
|
|
453
|
+
leptris_plan_free(plan);
|
|
454
|
+
leptris_document_free(doc);
|
|
455
|
+
}
|
|
456
|
+
|
|
389
457
|
TEST(PlanBuild, CopiesTheSpecStrings) {
|
|
390
458
|
LeptrisDocument doc = parse("<doc><t>v</t></doc>");
|
|
391
459
|
ASSERT_NE(doc, nullptr);
|
|
@@ -2234,6 +2234,62 @@ TEST(DomBasics, NodeVisitWalksSubtreeEnterLeave) {
|
|
|
2234
2234
|
leptris_document_free(doc);
|
|
2235
2235
|
}
|
|
2236
2236
|
|
|
2237
|
+
/* Issue #1332: entering-only visit mode — one callback per node, on
|
|
2238
|
+
* entering. Halves the C-callback invocations for consumers that
|
|
2239
|
+
* discard the leaving half (family walks bind lazily on enter).
|
|
2240
|
+
* Same walk order and depths as leptris_node_visit. */
|
|
2241
|
+
TEST(DomBasics, NodeVisitEnteringFiresOncePerNode) {
|
|
2242
|
+
const char xml[] = "<r><a>t1</a><b><!--c--></b></r>";
|
|
2243
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
2244
|
+
LeptrisDocument doc = leptris_parse_string(xml, std::strlen(xml), &st);
|
|
2245
|
+
ASSERT_NE(doc, nullptr);
|
|
2246
|
+
LeptrisElement root = leptris_document_root(doc);
|
|
2247
|
+
ASSERT_NE(root, nullptr);
|
|
2248
|
+
|
|
2249
|
+
VisitLog log;
|
|
2250
|
+
leptris_node_visit_entering((LeptrisNodeRef)root, &VisitLog::visit,
|
|
2251
|
+
&log);
|
|
2252
|
+
/* 5 nodes (r, a, text, b, comment) — the 3 leaving events of
|
|
2253
|
+
* the full mode are gone. The event list has no L* entries. */
|
|
2254
|
+
ASSERT_EQ(log.events.size(), 5u);
|
|
2255
|
+
EXPECT_EQ(log.events[0], "Er:0");
|
|
2256
|
+
EXPECT_EQ(log.events[1], "Ea:1");
|
|
2257
|
+
EXPECT_EQ(log.events[2], "T:2");
|
|
2258
|
+
EXPECT_EQ(log.events[3], "Eb:1");
|
|
2259
|
+
EXPECT_EQ(log.events[4], "O:2");
|
|
2260
|
+
|
|
2261
|
+
/* Document-node walk behaves identically to the full mode. */
|
|
2262
|
+
VisitLog dlog;
|
|
2263
|
+
leptris_node_visit_entering(leptris_document_node(doc),
|
|
2264
|
+
&VisitLog::visit, &dlog);
|
|
2265
|
+
ASSERT_EQ(dlog.events.size(), 5u);
|
|
2266
|
+
EXPECT_EQ(dlog.events[0], "Er:0");
|
|
2267
|
+
leptris_document_free(doc);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
TEST(DomBasics, NodeVisitEnteringHalvesTheCallbackCount) {
|
|
2271
|
+
/* The moxml-walk contract (#1332): elements visit TWICE in the
|
|
2272
|
+
* full mode, ONCE here. */
|
|
2273
|
+
const char xml[] =
|
|
2274
|
+
"<r><a><b/><c><d/></c></a><e/><f><g><h/></g></f></r>";
|
|
2275
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
2276
|
+
LeptrisDocument doc = leptris_parse_string(xml, std::strlen(xml), &st);
|
|
2277
|
+
ASSERT_NE(doc, nullptr);
|
|
2278
|
+
|
|
2279
|
+
int full_calls = 0, entering_calls = 0;
|
|
2280
|
+
auto counter = +[](void* ud, LeptrisNodeRef, int, int) {
|
|
2281
|
+
++*static_cast<int*>(ud);
|
|
2282
|
+
};
|
|
2283
|
+
leptris_node_visit((LeptrisNodeRef)leptris_document_root(doc),
|
|
2284
|
+
counter, &full_calls);
|
|
2285
|
+
leptris_node_visit_entering(
|
|
2286
|
+
(LeptrisNodeRef)leptris_document_root(doc), counter,
|
|
2287
|
+
&entering_calls);
|
|
2288
|
+
EXPECT_EQ(full_calls, 18); /* 9 elements * 2 */
|
|
2289
|
+
EXPECT_EQ(entering_calls, 9); /* 9 elements, once */
|
|
2290
|
+
leptris_document_free(doc);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2237
2293
|
TEST(DomBasics, NodeChildrenExCarriesKinds) {
|
|
2238
2294
|
const char xml[] =
|
|
2239
2295
|
"<r>text<!--c--><a/><?pi x?></r>";
|