leptris 1.9.240.0 → 1.9.242.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 +19 -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 +70 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +2 -2
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +56 -4
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +6 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +6 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +121 -7
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +62 -18
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +106 -6
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +266 -27
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +19 -20
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +193 -7
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +55 -85
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +147 -0
- 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: 4de40885119a37c57132fe2a16bb048198afa93b8e4077f8c3b4d428e176c753
|
|
4
|
+
data.tar.gz: 55353281eb7cd09b749905b35172a40d1be65821758ddffcac687fe77df44e52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62f07379ee5b97c1695845e7afd61eb0b1135171eb558feb7f8def8789d86488348c9f14616a67124cfbed8f4608c7f1d0af90793e37d54da755669687777f2e
|
|
7
|
+
data.tar.gz: e086c44b5d76e6e557c19c37cde928c7337e633f20dde340bf46e9ee3c95dd5c2285ea8c51ee5ca9a04a81b04e2f7b2cc605779268bb1c586240c4e58d6524cb
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.241.0] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.241 (riding 1.9.240 + 1.9.241):
|
|
13
|
+
- The interleaved parse lane now applies the same §2.11 EOL
|
|
14
|
+
normalization as the classic lane (#1357) — both lanes
|
|
15
|
+
spec-identical; the lane stays env-gated off by default.
|
|
16
|
+
- xquery: order by — only a genuinely empty-sequence key takes
|
|
17
|
+
the empty mode (an empty STRING key sorts by value);
|
|
18
|
+
fn:distinct-values compares xs:duration-family members in the
|
|
19
|
+
value space and spells survivors canonically.
|
|
20
|
+
- xquery: shortest round-trip plain spelling for numbers — the
|
|
21
|
+
15-digit plain budget collided distinct decimals
|
|
22
|
+
(1.2000000000000001 vs 1.2); the branch now discovers minimal
|
|
23
|
+
significant digits and renders fixed-point uncapped.
|
|
24
|
+
- Repairs the Rakefile engine pin, left at 1.9.239 by the last
|
|
25
|
+
release's version-only bump, back in lockstep with version.rb.
|
|
26
|
+
|
|
8
27
|
## [1.9.239.0] - 2026-09-25
|
|
9
28
|
|
|
10
29
|
### Fixed
|
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.246"
|
|
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,75 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.246] - 2026-09-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- exact decimal lexical carrier (xquery)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [1.9.245] - 2026-09-25
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- array:remove + NaN subsequence 2-arg adoption (xquery)
|
|
16
|
+
- attribute ctor carriers + derived-string deep-equal class (xquery)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## [1.9.244] - 2026-09-25
|
|
21
|
+
|
|
22
|
+
<!-- Edit this section with the actual release notes. -->
|
|
23
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- (describe changes here)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## [1.9.243] - 2026-09-25
|
|
31
|
+
|
|
32
|
+
<!-- Edit this section with the actual release notes. -->
|
|
33
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- (describe changes here)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## [1.9.242] - 2026-09-25
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- QT3 maps/arrays slice — empty-aggregate arity guard + canonical map order (xquery)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## [1.9.241] - 2026-09-25
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- xquery: shortest round-trip plain spelling for XQuery numbers.
|
|
53
|
+
The plain branch kept a 15-digit budget, so `1.2000000000000001`
|
|
54
|
+
and `1.2` collided into one spelling and fn:distinct-values
|
|
55
|
+
deduped distinct xs:decimals. The branch now discovers the minimal
|
|
56
|
+
significant digits via the same e-form loop (float-aware) and
|
|
57
|
+
renders fixed-point with `decimals = prec - exp10` — uncapped, or
|
|
58
|
+
tiny plain-range values (`1e-18`) truncate to "0".
|
|
59
|
+
- QT3 corpus: distinct-values 93→94 adopted; pins-off reds 67→59.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## [1.9.240] - 2026-09-25
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- order-by empty-string keys, duration-typed distinct-values (xquery)
|
|
68
|
+
- qualifier casts in the plan free path (descriptor)
|
|
69
|
+
- §2.11 EOL normalization in the interleaved lane (#326) (dom)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
3
73
|
## [1.9.239] - 2026-09-24
|
|
4
74
|
|
|
5
75
|
### Added
|
|
@@ -244,7 +244,7 @@ static void dp_plan_free(LeptrisPlan p) {
|
|
|
244
244
|
for (uint32_t i = 0; i < p->plan_count; i++) {
|
|
245
245
|
dp_plan* d = &p->plans[i];
|
|
246
246
|
for (uint32_t a = 0; a < d->attribute_count; a++) {
|
|
247
|
-
free(d->attribute_plans[a].wire_name);
|
|
247
|
+
free((char *)d->attribute_plans[a].wire_name);
|
|
248
248
|
for (uint16_t pi = 0;
|
|
249
249
|
pi < d->attribute_plans[a].predicate_count; pi++) {
|
|
250
250
|
free((char*)d->attribute_plans[a]
|
|
@@ -255,7 +255,7 @@ static void dp_plan_free(LeptrisPlan p) {
|
|
|
255
255
|
free((leptris_attr_predicate*)d->attribute_plans[a].predicates);
|
|
256
256
|
}
|
|
257
257
|
for (uint32_t c = 0; c < d->child_count; c++) {
|
|
258
|
-
free(d->child_plans[c].wire_name);
|
|
258
|
+
free((char *)d->child_plans[c].wire_name);
|
|
259
259
|
for (uint16_t pi = 0;
|
|
260
260
|
pi < d->child_plans[c].predicate_count; pi++) {
|
|
261
261
|
free((char*)d->child_plans[c]
|
|
@@ -2290,6 +2290,23 @@ int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
|
|
|
2290
2290
|
j = (size_t)(vend - s) + 1;
|
|
2291
2291
|
IlAttr a = { (uint32_t)a0, (uint32_t)al,
|
|
2292
2292
|
(uint32_t)v0, (uint32_t)vl, aws };
|
|
2293
|
+
/* XML 1.0 §3.1: duplicate attributes are a fatal error —
|
|
2294
|
+
* the classic lane reports "Attribute %s redefined" and
|
|
2295
|
+
* the SAX callback path records an ERROR event, but the
|
|
2296
|
+
* record table has no error channel. A dup-containing
|
|
2297
|
+
* document is therefore outside the scannable subset:
|
|
2298
|
+
* bail so callers fall back to the callback path (#281,
|
|
2299
|
+
* leptris-ruby#331 adoption). Same-element prior attrs
|
|
2300
|
+
* are the contiguous range [r.first_attr, nattr). */
|
|
2301
|
+
if (acount > 0) {
|
|
2302
|
+
for (uint32_t k = r.first_attr; k < (uint32_t)c->nattr;
|
|
2303
|
+
k++) {
|
|
2304
|
+
const IlAttr* p = &c->attrs[k];
|
|
2305
|
+
if (p->name_len == (uint32_t)al &&
|
|
2306
|
+
memcmp(s + p->name_off, s + a0, al) == 0)
|
|
2307
|
+
return 0;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2293
2310
|
if (!il_push_attr(c, &a)) return 0;
|
|
2294
2311
|
if (acount == 0) r.first_attr = (uint32_t)(c->nattr - 1);
|
|
2295
2312
|
acount++;
|
|
@@ -2560,12 +2577,22 @@ static struct leptris_document* dp_il_build(
|
|
|
2560
2577
|
pool, a->val_len + 1);
|
|
2561
2578
|
if (!norm) { free(smap); free(rdepth);
|
|
2562
2579
|
free(lc); goto oom_pool; }
|
|
2580
|
+
/* §2.11 (#326): CRLF is ONE line break — one
|
|
2581
|
+
* space, same collapse as the classic lane. */
|
|
2582
|
+
size_t nw = 0;
|
|
2563
2583
|
for (uint32_t w = 0; w < a->val_len; w++) {
|
|
2564
2584
|
char ch = scratch[a->val_off + w];
|
|
2565
|
-
|
|
2566
|
-
|
|
2585
|
+
if (ch == '\r') {
|
|
2586
|
+
norm[nw++] = ' ';
|
|
2587
|
+
if (w + 1 < a->val_len &&
|
|
2588
|
+
scratch[a->val_off + w + 1] == '\n')
|
|
2589
|
+
w++;
|
|
2590
|
+
} else {
|
|
2591
|
+
norm[nw++] = (ch == '\t' || ch == '\n')
|
|
2592
|
+
? ' ' : ch;
|
|
2593
|
+
}
|
|
2567
2594
|
}
|
|
2568
|
-
norm[
|
|
2595
|
+
norm[nw] = '\0';
|
|
2569
2596
|
leptris_attr_value_set_heap(
|
|
2570
2597
|
at, leptris_sv_from_cstr(norm));
|
|
2571
2598
|
} else {
|
|
@@ -2672,12 +2699,37 @@ static struct leptris_document* dp_il_build(
|
|
|
2672
2699
|
} else {
|
|
2673
2700
|
LeptrisTextNode* tn = &tblk[ntext++];
|
|
2674
2701
|
const char* content = scratch + r->off;
|
|
2702
|
+
size_t content_len = r->len;
|
|
2675
2703
|
scratch[r->off + r->len] = '\0';
|
|
2704
|
+
/* §2.11 (#326), interleaved lane: same pre-expansion
|
|
2705
|
+
* CRLF/CR -> LF normalization as the classic lane;
|
|
2706
|
+
* CR-free runs stay in scratch. */
|
|
2707
|
+
if (memchr(content, '\r', content_len)) {
|
|
2708
|
+
char* norm =
|
|
2709
|
+
(char*)leptris_pool_alloc(pool, content_len + 1);
|
|
2710
|
+
if (!norm) { free(smap); free(rdepth);
|
|
2711
|
+
free(lc); goto oom_pool; }
|
|
2712
|
+
char* w = norm;
|
|
2713
|
+
const char* rr = content;
|
|
2714
|
+
const char* r_end = content + content_len;
|
|
2715
|
+
while (rr < r_end) {
|
|
2716
|
+
if (*rr == '\r') {
|
|
2717
|
+
*w++ = '\n';
|
|
2718
|
+
if (rr + 1 < r_end && rr[1] == '\n') rr++;
|
|
2719
|
+
} else {
|
|
2720
|
+
*w++ = *rr;
|
|
2721
|
+
}
|
|
2722
|
+
rr++;
|
|
2723
|
+
}
|
|
2724
|
+
*w = '\0';
|
|
2725
|
+
content = norm;
|
|
2726
|
+
content_len = (size_t)(w - norm);
|
|
2727
|
+
}
|
|
2676
2728
|
tn->base.type = LEPTRIS_NODE_TYPE_TEXT;
|
|
2677
2729
|
tn->base.frozen = 1;
|
|
2678
2730
|
tn->base.line = r->line;
|
|
2679
2731
|
leptris_textnode_set_content_ptr_doc(tn, content, doc);
|
|
2680
|
-
tn->content_len =
|
|
2732
|
+
tn->content_len = (uint32_t)content_len;
|
|
2681
2733
|
/* (slice 4: pool/borrowed fields gone; the il scratch
|
|
2682
2734
|
* run is already NUL-terminated at carve) */
|
|
2683
2735
|
tn->parent_off = 0;
|
|
@@ -659,6 +659,12 @@ struct leptris_xpath_result {
|
|
|
659
659
|
* round-trip E form instead of the libxml2-parity XPath
|
|
660
660
|
* spelling. Cleared in result_new (free-list recycle). */
|
|
661
661
|
int xq_spelling;
|
|
662
|
+
/* Canonical decimal lexical: set by the xs:decimal ctor — the
|
|
663
|
+
* double carrier cannot hold 30 significant digits, so
|
|
664
|
+
* eq-based functions (distinct-values, index-of, deep-equal)
|
|
665
|
+
* key on the canonical form instead (decimal-lex slice). OWNED
|
|
666
|
+
* — freed in result_free, cleared in result_new (recycle). */
|
|
667
|
+
char* decimal_lex;
|
|
662
668
|
};
|
|
663
669
|
|
|
664
670
|
/* Namespace mapping for XPath context (v0.8.0) */
|
|
@@ -744,6 +744,7 @@ struct leptris_xpath_result* xpath_result_new(XPathResultType type) {
|
|
|
744
744
|
result->is_int = 0; /* free-list recycling would leak it */
|
|
745
745
|
result->atomic_type = NULL;
|
|
746
746
|
result->xq_spelling = 0;
|
|
747
|
+
result->decimal_lex = NULL; /* free-list recycling would leak it */
|
|
747
748
|
|
|
748
749
|
/* Initialize union based on type. XPATH_RESULT_CACHED is the
|
|
749
750
|
* free-list sentinel — a fresh result never carries it. */
|
|
@@ -773,6 +774,11 @@ void xpath_result_free(struct leptris_xpath_result* result) {
|
|
|
773
774
|
* no-op, never a second payload release. */
|
|
774
775
|
if (result->type == XPATH_RESULT_CACHED) return;
|
|
775
776
|
|
|
777
|
+
if (result->decimal_lex) {
|
|
778
|
+
LEPTRIS_FREE(result->decimal_lex);
|
|
779
|
+
result->decimal_lex = NULL;
|
|
780
|
+
}
|
|
781
|
+
|
|
776
782
|
/* Release the payload through locals: after this block the
|
|
777
783
|
* union slot is dead, and parking the free-list next-pointer
|
|
778
784
|
* into it cannot alias the memory just released. */
|
|
@@ -418,7 +418,9 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
418
418
|
op0 != XPATH_OP_ELEMENT_CTOR &&
|
|
419
419
|
op0 != XPATH_OP_ATTRIBUTE_CTOR &&
|
|
420
420
|
op0 != XPATH_OP_TEXT_CTOR &&
|
|
421
|
-
op0 != XPATH_OP_DOCUMENT_CTOR
|
|
421
|
+
op0 != XPATH_OP_DOCUMENT_CTOR &&
|
|
422
|
+
op0 != XPATH_OP_ARRAY_CONSTRUCTOR &&
|
|
423
|
+
op0 != XPATH_OP_MAP_CONSTRUCTOR)
|
|
422
424
|
return NULL;
|
|
423
425
|
|
|
424
426
|
XPathOperatorType op = (XPathOperatorType)ast->number_value;
|
|
@@ -1597,6 +1599,38 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
1597
1599
|
if (v) xpath_result_free(v);
|
|
1598
1600
|
}
|
|
1599
1601
|
if (!s) s = leptris_strdup("");
|
|
1602
|
+
if (op == XPATH_OP_ATTRIBUTE_CTOR) {
|
|
1603
|
+
/* Top-level attribute ctor keeps its name: one
|
|
1604
|
+
* synthetic member carrying "\x03A" name "\x01"
|
|
1605
|
+
* value — deep-equal compares name+value
|
|
1606
|
+
* (K2-SeqDeepEqualFunc-25/31/32), string consumers
|
|
1607
|
+
* strip to the value. \x01 cannot occur in XML
|
|
1608
|
+
* content. */
|
|
1609
|
+
const char* an = ast->value ? ast->value : "";
|
|
1610
|
+
size_t alen = strlen(an), vlen = strlen(s);
|
|
1611
|
+
size_t clen = alen + vlen + 3;
|
|
1612
|
+
char* carrier = (char*)malloc(clen + 1);
|
|
1613
|
+
if (!carrier) { free(s); return NULL; }
|
|
1614
|
+
carrier[0] = '\x03';
|
|
1615
|
+
carrier[1] = 'A';
|
|
1616
|
+
memcpy(carrier + 2, an, alen);
|
|
1617
|
+
carrier[2 + alen] = '\x01';
|
|
1618
|
+
memcpy(carrier + 3 + alen, s, vlen + 1);
|
|
1619
|
+
free(s);
|
|
1620
|
+
XPathNodeSet* ns = xpath_nodeset_new();
|
|
1621
|
+
if (!ns) { free(carrier); return NULL; }
|
|
1622
|
+
ns->owns_synthetic_text = 1;
|
|
1623
|
+
ns->is_sequence = 1;
|
|
1624
|
+
XPathTextNode* tn = synth_text(carrier, clen);
|
|
1625
|
+
free(carrier);
|
|
1626
|
+
if (!tn) { xpath_nodeset_free(ns); return NULL; }
|
|
1627
|
+
xpath_nodeset_add(ns, tn);
|
|
1628
|
+
struct leptris_xpath_result* out =
|
|
1629
|
+
xpath_result_new(XPATH_RESULT_NODESET);
|
|
1630
|
+
if (!out) { xpath_nodeset_free(ns); return NULL; }
|
|
1631
|
+
out->value.nodeset_value = ns;
|
|
1632
|
+
return out;
|
|
1633
|
+
}
|
|
1600
1634
|
struct leptris_xpath_result* out =
|
|
1601
1635
|
xpath_result_new(XPATH_RESULT_STRING);
|
|
1602
1636
|
if (!out) { free(s); return NULL; }
|
|
@@ -1903,6 +1937,81 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
1903
1937
|
free(v);
|
|
1904
1938
|
}
|
|
1905
1939
|
buf[len] = '\0';
|
|
1940
|
+
/* Canonical entry order: sort by key so deep-equal and the
|
|
1941
|
+
* serialized carrier are key-order-insensitive (maps-4:
|
|
1942
|
+
* {1:a, 2:b} = {2:b, 1:a}); map iteration order is
|
|
1943
|
+
* implementation-defined in XQuery. */
|
|
1944
|
+
{
|
|
1945
|
+
size_t body_len = len - 4; /* past "\x03MAP" */
|
|
1946
|
+
char* body = buf + 4;
|
|
1947
|
+
size_t count = 0;
|
|
1948
|
+
for (size_t i = 0; i < body_len; i++)
|
|
1949
|
+
if (body[i] == '\x02') count++;
|
|
1950
|
+
if (count > 1) {
|
|
1951
|
+
char** keys = (char**)malloc(count * sizeof(char*));
|
|
1952
|
+
size_t* klens = (size_t*)malloc(count * sizeof(size_t));
|
|
1953
|
+
char** vals = (char**)malloc(count * sizeof(char*));
|
|
1954
|
+
size_t* vlens = (size_t*)malloc(count * sizeof(size_t));
|
|
1955
|
+
if (keys && klens && vals && vlens) {
|
|
1956
|
+
size_t n = 0, p = 0;
|
|
1957
|
+
while (p < body_len) {
|
|
1958
|
+
if (body[p] != '\x02') { p++; continue; }
|
|
1959
|
+
p++;
|
|
1960
|
+
size_t ks = p;
|
|
1961
|
+
while (p < body_len && body[p] != '\x01') p++;
|
|
1962
|
+
keys[n] = body + ks;
|
|
1963
|
+
klens[n] = p - ks;
|
|
1964
|
+
p++;
|
|
1965
|
+
size_t vs = p;
|
|
1966
|
+
while (p < body_len && body[p] != '\x02') p++;
|
|
1967
|
+
vals[n] = body + vs;
|
|
1968
|
+
vlens[n] = p - vs;
|
|
1969
|
+
n++;
|
|
1970
|
+
}
|
|
1971
|
+
for (size_t a = 1; a < n; a++) {
|
|
1972
|
+
char* ek = keys[a]; size_t ekl = klens[a];
|
|
1973
|
+
char* ev = vals[a]; size_t evl = vlens[a];
|
|
1974
|
+
size_t b2 = a;
|
|
1975
|
+
while (b2 > 0) {
|
|
1976
|
+
int c = strncmp(keys[b2 - 1], ek,
|
|
1977
|
+
klens[b2 - 1] < ekl
|
|
1978
|
+
? klens[b2 - 1] : ekl);
|
|
1979
|
+
if (c == 0)
|
|
1980
|
+
c = klens[b2 - 1] < ekl ? -1
|
|
1981
|
+
: klens[b2 - 1] > ekl ? 1 : 0;
|
|
1982
|
+
if (c <= 0) break;
|
|
1983
|
+
keys[b2] = keys[b2 - 1];
|
|
1984
|
+
klens[b2] = klens[b2 - 1];
|
|
1985
|
+
vals[b2] = vals[b2 - 1];
|
|
1986
|
+
vlens[b2] = vlens[b2 - 1];
|
|
1987
|
+
b2--;
|
|
1988
|
+
}
|
|
1989
|
+
keys[b2] = ek; klens[b2] = ekl;
|
|
1990
|
+
vals[b2] = ev; vlens[b2] = evl;
|
|
1991
|
+
}
|
|
1992
|
+
/* Sort-order permutes segments in place: write
|
|
1993
|
+
* through a scratch (a forward write clobbers a
|
|
1994
|
+
* not-yet-copied source segment). */
|
|
1995
|
+
char* scratch = (char*)malloc(body_len + 1);
|
|
1996
|
+
if (scratch) {
|
|
1997
|
+
size_t w2 = 0;
|
|
1998
|
+
for (size_t e = 0; e < n; e++) {
|
|
1999
|
+
scratch[w2++] = '\x02';
|
|
2000
|
+
memcpy(scratch + w2, keys[e], klens[e]);
|
|
2001
|
+
w2 += klens[e];
|
|
2002
|
+
scratch[w2++] = '\x01';
|
|
2003
|
+
memcpy(scratch + w2, vals[e], vlens[e]);
|
|
2004
|
+
w2 += vlens[e];
|
|
2005
|
+
}
|
|
2006
|
+
memcpy(body, scratch, w2);
|
|
2007
|
+
len = 4 + w2;
|
|
2008
|
+
buf[len] = '\0';
|
|
2009
|
+
free(scratch);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
free(keys); free(klens); free(vals); free(vlens);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1906
2015
|
XPathNodeSet* out = xpath_nodeset_new();
|
|
1907
2016
|
if (!out) { free(buf); return NULL; }
|
|
1908
2017
|
out->owns_synthetic_text = 1;
|
|
@@ -2319,12 +2428,17 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
2319
2428
|
is->owns_synthetic_text = 0;
|
|
2320
2429
|
} else {
|
|
2321
2430
|
char* piece =
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2431
|
+
item->decimal_lex
|
|
2432
|
+
? leptris_strdup(item->decimal_lex)
|
|
2433
|
+
: (ctx->xquery_spelling &&
|
|
2434
|
+
item->type == XPATH_RESULT_NUMBER &&
|
|
2435
|
+
!item->is_int)
|
|
2436
|
+
? xpath_number_to_string_xq_typed(
|
|
2437
|
+
item->value.number_value,
|
|
2438
|
+
item->atomic_type &&
|
|
2439
|
+
strcmp(item->atomic_type,
|
|
2440
|
+
"xs:float") == 0)
|
|
2441
|
+
: xpath_to_string(item);
|
|
2328
2442
|
if (item->type == XPATH_RESULT_NUMBER) {
|
|
2329
2443
|
/* "\x03N" marks numeric members for per-member
|
|
2330
2444
|
* type checks (instance of); get_node_text
|
|
@@ -136,17 +136,32 @@ char* xpath_number_to_string_xq_typed(double number, int float_prec) {
|
|
|
136
136
|
snprintf(work, sizeof(work), "%sE%s", mant, exp);
|
|
137
137
|
return leptris_strdup(work);
|
|
138
138
|
} else {
|
|
139
|
-
/*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
/* Shortest round-trip PLAIN spelling: discover the minimal
|
|
140
|
+
* significant-digit count via the e-form (float-aware like
|
|
141
|
+
* the scientific branch), then render fixed-point. The old
|
|
142
|
+
* 15-digit budget truncated the 17th digit, colliding
|
|
143
|
+
* distinct xs:decimals ("1.2" vs "1.2000000000000002")
|
|
144
|
+
* into one spelling. */
|
|
145
|
+
char mant[64];
|
|
146
|
+
int prec;
|
|
147
|
+
for (prec = 1; prec <= 17; prec++) {
|
|
148
|
+
snprintf(mant, sizeof(mant), "%.*e", prec, number);
|
|
149
|
+
double back = strtod(mant, NULL);
|
|
150
|
+
if (float_prec) back = (double)((float)back);
|
|
151
|
+
if (back == number) break;
|
|
152
|
+
}
|
|
153
|
+
int exp10 = 0;
|
|
154
|
+
char* e = strchr(mant, 'e');
|
|
155
|
+
if (e) exp10 = atoi(e + 1);
|
|
156
|
+
/* %.*e prints `prec` digits AFTER the mantissa point, so
|
|
157
|
+
* fixed-point decimals = prec - exp10 ("2.5e+00" -> 1). */
|
|
158
|
+
int decimals = prec - exp10;
|
|
159
|
+
if (decimals < 0) decimals = 0;
|
|
160
|
+
/* tiny plain-range values (1e-18) need exp10 digits of
|
|
161
|
+
* fraction — no 17 cap here or they truncate to "0" */
|
|
162
|
+
if (decimals > 40) decimals = 40;
|
|
163
|
+
size = (int)snprintf(work, sizeof(work), "%.*f",
|
|
164
|
+
decimals, number);
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
char* start = work;
|
|
@@ -261,11 +276,18 @@ char* get_node_text(void* node) {
|
|
|
261
276
|
* per-member type checks (instance of). \x03 is invalid
|
|
262
277
|
* in XML 1.0 text, so it can never collide with real
|
|
263
278
|
* content — strip it for every string consumer. */
|
|
264
|
-
if (c[0] == '\x03'
|
|
265
|
-
(c[1] == '
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
if (c[0] == '\x03') {
|
|
280
|
+
if (c[1] == 'A') {
|
|
281
|
+
/* attribute ctor carrier: string value is the
|
|
282
|
+
* part after the "\x01" separator */
|
|
283
|
+
const char* sep = strchr(c, '\x01');
|
|
284
|
+
return leptris_strdup(sep ? sep + 1 : "");
|
|
285
|
+
}
|
|
286
|
+
if (c[1] == 'N' || c[1] == 'B' || c[1] == 'D' ||
|
|
287
|
+
(c[1] == 'F' &&
|
|
288
|
+
!((c[2] == 'N' && c[3] == '\x02') || c[2] == 'R')))
|
|
289
|
+
c += 2;
|
|
290
|
+
}
|
|
269
291
|
return leptris_strdup(c);
|
|
270
292
|
}
|
|
271
293
|
|
|
@@ -284,8 +306,30 @@ int xpath_to_boolean(struct leptris_xpath_result* result) {
|
|
|
284
306
|
return result->value.number_value != 0.0 && !isnan(result->value.number_value);
|
|
285
307
|
case XPATH_RESULT_STRING:
|
|
286
308
|
return result->value.string_value && result->value.string_value[0] != '\0';
|
|
287
|
-
case XPATH_RESULT_NODESET:
|
|
288
|
-
|
|
309
|
+
case XPATH_RESULT_NODESET: {
|
|
310
|
+
XPathNodeSet* ns = result->value.nodeset_value;
|
|
311
|
+
size_t cnt = ns ? xpath_nodeset_count(ns) : 0;
|
|
312
|
+
/* XQuery sequence EBV: a singleton atomic carries the
|
|
313
|
+
* item's EBV (fn:remove round-trips a false boolean as
|
|
314
|
+
* falsy), while XPath node-existence EBV applies to
|
|
315
|
+
* real nodes and multi-item sequences. */
|
|
316
|
+
if (!ns || !ns->is_sequence) return cnt > 0;
|
|
317
|
+
if (cnt != 1) return cnt > 0;
|
|
318
|
+
void* nd = ns->nodes[0];
|
|
319
|
+
if (!nd || XPATH_NODE_TYPE(nd) != LEPTRIS_NODE_TEXT)
|
|
320
|
+
return 1;
|
|
321
|
+
const char* c = ((XPathTextNode*)nd)->content;
|
|
322
|
+
if (!c) return 1;
|
|
323
|
+
if (c[0] == '\x03') {
|
|
324
|
+
if (c[1] == 'B') return c[2] == 't';
|
|
325
|
+
if (c[1] == 'N' || c[1] == 'F' || c[1] == 'D') {
|
|
326
|
+
double d = strtod(c + 2, NULL);
|
|
327
|
+
return d != 0.0 && !isnan(d);
|
|
328
|
+
}
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
return c[0] != '\0';
|
|
332
|
+
}
|
|
289
333
|
default:
|
|
290
334
|
return 0;
|
|
291
335
|
}
|