leptris 1.9.241.0 → 1.9.248.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 +16 -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 +61 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +17 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +10 -1
- data/vendor-src/libleptris/src/leptris/leptris.c +16 -2
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +6 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +47 -1
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +121 -7
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +36 -7
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +106 -6
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +171 -22
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +19 -20
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +176 -3
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +55 -75
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +192 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1445126f3a7bced66c8ccb08c13e6d61f0a8ffc95f154bb091aedd07e54ef706
|
|
4
|
+
data.tar.gz: c0c39cf6600337d568dfae5237f650a5b3aa1ebab16b42a648fff456c9d8f050
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35a1e9c1637cdfb8d2556d6ce9e10b5fce62b369f5824ae4b9a941026dee156bc54aa5d8dc9d30ba2fe25e8403d096c45449234ed29b422ce5d12febeca9d7ec
|
|
7
|
+
data.tar.gz: 36a555006ae5e9bb900eaa2f4955c337e4251dbfdd58be20ba91b4825b79de86a6fd8a8b161608c4f71c3d49422d68f3c97e6720e0baceac47e6de7cd0a8e26d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.248.0] - 2026-09-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.248 (riding 1.9.247 + 1.9.248):
|
|
13
|
+
- xpath: filtering a non-nodeset base treats the atom as a
|
|
14
|
+
one-item sequence — `(1)[1]` keeps the atom and `(1)[1] + 1`
|
|
15
|
+
is 2 (was an unconditional empty nodeset).
|
|
16
|
+
- html: the document arena span is sized from the input once
|
|
17
|
+
(#1218) — borrowed pointers stay in the int32 compact range,
|
|
18
|
+
removing ~776k overflow-table inserts on a 2.6 MB parse;
|
|
19
|
+
measured 31.4 -> 50.9 MB/s (+62%) on the table-rows corpus.
|
|
20
|
+
- Restores the naming lockstep: the previous release vendored
|
|
21
|
+
engine 1.9.246 under the version 1.9.242.0; version.rb now
|
|
22
|
+
matches the vendored engine semver again.
|
|
23
|
+
|
|
8
24
|
## [1.9.241.0] - 2026-09-25
|
|
9
25
|
|
|
10
26
|
### 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.248"
|
|
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,66 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.248] - 2026-09-26
|
|
4
|
+
|
|
5
|
+
<!-- Edit this section with the actual release notes. -->
|
|
6
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- (describe changes here)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [1.9.247] - 2026-09-26
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- filter on a non-nodeset base treats the atom as a one-item sequence (xpath)
|
|
18
|
+
|
|
19
|
+
## [1.9.246] - 2026-09-25
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- exact decimal lexical carrier (xquery)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## [1.9.245] - 2026-09-25
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- array:remove + NaN subsequence 2-arg adoption (xquery)
|
|
32
|
+
- attribute ctor carriers + derived-string deep-equal class (xquery)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## [1.9.244] - 2026-09-25
|
|
37
|
+
|
|
38
|
+
<!-- Edit this section with the actual release notes. -->
|
|
39
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- (describe changes here)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## [1.9.243] - 2026-09-25
|
|
47
|
+
|
|
48
|
+
<!-- Edit this section with the actual release notes. -->
|
|
49
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- (describe changes here)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## [1.9.242] - 2026-09-25
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- QT3 maps/arrays slice — empty-aggregate arity guard + canonical map order (xquery)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
3
64
|
## [1.9.241] - 2026-09-25
|
|
4
65
|
|
|
5
66
|
### Fixed
|
|
@@ -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++;
|
|
@@ -5453,8 +5453,17 @@ static LeptrisDocument html_parse_shared(
|
|
|
5453
5453
|
if (status) *status = LEPTRIS_ERROR_NULL_ARG;
|
|
5454
5454
|
return NULL;
|
|
5455
5455
|
}
|
|
5456
|
+
/* #1218: size the arena span from the input up front — the
|
|
5457
|
+
* HTML tree expands ~8x (nodes + attributes + the owned input
|
|
5458
|
+
* copy), and an undersized span spills later allocations into
|
|
5459
|
+
* extension blocks in a different malloc region, pushing every
|
|
5460
|
+
* borrowed pointer out of the int32 compact range (measured
|
|
5461
|
+
* 776k overflow-table inserts on the 2.6 MB table shape). */
|
|
5462
|
+
extern struct leptris_document* leptris_document_create_with_arena_size(
|
|
5463
|
+
size_t arena_size);
|
|
5456
5464
|
struct leptris_document* doc =
|
|
5457
|
-
(struct leptris_document*)
|
|
5465
|
+
(struct leptris_document*)leptris_document_create_with_arena_size(
|
|
5466
|
+
len * 10 + (1u << 18));
|
|
5458
5467
|
if (!doc) {
|
|
5459
5468
|
if (status) *status = LEPTRIS_ERROR_MEMORY;
|
|
5460
5469
|
return NULL;
|
|
@@ -173,7 +173,17 @@ void leptris_parse_options_init(leptris_parse_options* opts) {
|
|
|
173
173
|
* pool-allocated (TODO 154) zeroed struct, strict mode inherited
|
|
174
174
|
* from the thread-local setting, standalone unset (-1).
|
|
175
175
|
*/
|
|
176
|
-
|
|
176
|
+
/* #1218: parse entry points that know their input size reserve the
|
|
177
|
+
* arena span ONCE at content-derived scale — an undersized span
|
|
178
|
+
* overflows into tracked extension blocks, which malloc places in a
|
|
179
|
+
* different region (measured 105 TB away on macOS, ~5.5 GB from the
|
|
180
|
+
* span). Every borrowed pointer (text runs, element names, attribute
|
|
181
|
+
* strings) then exceeds the int32 compact range and pays an
|
|
182
|
+
* overflow-table insert per field plus a hash lookup per decode for
|
|
183
|
+
* the REST of the parse. Sized spans keep the whole parse in one
|
|
184
|
+
* contiguous region. */
|
|
185
|
+
struct leptris_document* leptris_document_create_with_arena_size(
|
|
186
|
+
size_t arena_size) {
|
|
177
187
|
extern LeptrisMemoryPool* leptris_pool_create_arena_backed(
|
|
178
188
|
LeptrisArena*, int);
|
|
179
189
|
extern void* leptris_pool_alloc(LeptrisMemoryPool* pool, size_t size);
|
|
@@ -185,7 +195,7 @@ LEPTRIS_API LeptrisDocument leptris_document_create(void) {
|
|
|
185
195
|
* documents take the same arena-backed pool the parser uses
|
|
186
196
|
* (overflow extends via tracked blocks; the pool's page
|
|
187
197
|
* machinery no longer runs for documents). */
|
|
188
|
-
LeptrisArena* arena = leptris_arena_create(
|
|
198
|
+
LeptrisArena* arena = leptris_arena_create(arena_size);
|
|
189
199
|
if (!arena) return NULL;
|
|
190
200
|
LeptrisMemoryPool* pool = leptris_pool_create_arena_backed(arena, 1);
|
|
191
201
|
if (!pool) {
|
|
@@ -210,6 +220,10 @@ LEPTRIS_API LeptrisDocument leptris_document_create(void) {
|
|
|
210
220
|
return doc;
|
|
211
221
|
}
|
|
212
222
|
|
|
223
|
+
LEPTRIS_API LeptrisDocument leptris_document_create(void) {
|
|
224
|
+
return leptris_document_create_with_arena_size(64 * 1024);
|
|
225
|
+
}
|
|
226
|
+
|
|
213
227
|
LEPTRIS_API LeptrisDocument leptris_document_create_html(void) {
|
|
214
228
|
struct leptris_document* doc = leptris_document_create();
|
|
215
229
|
if (doc) doc->html_mode = 1;
|
|
@@ -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. */
|
|
@@ -979,8 +985,48 @@ struct leptris_xpath_result* evaluate_expr(XPathContext* ctx, XPathASTNode* ast)
|
|
|
979
985
|
if (!base) return NULL;
|
|
980
986
|
if (base->type != XPATH_RESULT_NODESET ||
|
|
981
987
|
!base->value.nodeset_value) {
|
|
988
|
+
/* Non-nodeset base: the atom is a one-item sequence
|
|
989
|
+
* — (1)[1] keeps it, (1)[true()] keeps it, (1)[2]
|
|
990
|
+
* empties (the old code returned empty
|
|
991
|
+
* unconditionally). The predicate sees position 1
|
|
992
|
+
* of 1; the context node stays the outer one. */
|
|
993
|
+
int keep = 1;
|
|
994
|
+
for (size_t p = 1; p < ast->child_count && keep; p++) {
|
|
995
|
+
XPathASTNode* pred = ast->children[p];
|
|
996
|
+
if (pred->type == XPATH_AST_NUMBER) {
|
|
997
|
+
keep = pred->number_value == 1.0;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
LeptrisElement old_node = ctx->context_node;
|
|
1001
|
+
size_t old_pos = ctx->context_position;
|
|
1002
|
+
size_t old_size = ctx->context_size;
|
|
1003
|
+
void* old_pred = ctx->current_predicate_node;
|
|
1004
|
+
ctx->context_position = 1;
|
|
1005
|
+
ctx->context_size = 1;
|
|
1006
|
+
struct leptris_xpath_result* pr =
|
|
1007
|
+
evaluate_expr(ctx, pred);
|
|
1008
|
+
ctx->context_node = old_node;
|
|
1009
|
+
ctx->context_position = old_pos;
|
|
1010
|
+
ctx->context_size = old_size;
|
|
1011
|
+
ctx->current_predicate_node = old_pred;
|
|
1012
|
+
if (!pr) {
|
|
1013
|
+
keep = 0;
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (pr->type == XPATH_RESULT_NUMBER)
|
|
1017
|
+
keep =
|
|
1018
|
+
(size_t)pr->value.number_value == 1;
|
|
1019
|
+
else
|
|
1020
|
+
keep = xpath_to_boolean(pr) ? 1 : 0;
|
|
1021
|
+
xpath_result_free(pr);
|
|
1022
|
+
}
|
|
1023
|
+
if (keep) return base;
|
|
982
1024
|
xpath_result_free(base);
|
|
983
|
-
|
|
1025
|
+
struct leptris_xpath_result* empty =
|
|
1026
|
+
xpath_result_new(XPATH_RESULT_NODESET);
|
|
1027
|
+
if (empty && empty->value.nodeset_value)
|
|
1028
|
+
empty->value.nodeset_value->is_sequence = 1;
|
|
1029
|
+
return empty;
|
|
984
1030
|
}
|
|
985
1031
|
if (ast->child_count > 1) {
|
|
986
1032
|
apply_predicates(ctx, base->value.nodeset_value,
|
|
@@ -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
|
|
@@ -276,11 +276,18 @@ char* get_node_text(void* node) {
|
|
|
276
276
|
* per-member type checks (instance of). \x03 is invalid
|
|
277
277
|
* in XML 1.0 text, so it can never collide with real
|
|
278
278
|
* content — strip it for every string consumer. */
|
|
279
|
-
if (c[0] == '\x03'
|
|
280
|
-
(c[1] == '
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
}
|
|
284
291
|
return leptris_strdup(c);
|
|
285
292
|
}
|
|
286
293
|
|
|
@@ -299,8 +306,30 @@ int xpath_to_boolean(struct leptris_xpath_result* result) {
|
|
|
299
306
|
return result->value.number_value != 0.0 && !isnan(result->value.number_value);
|
|
300
307
|
case XPATH_RESULT_STRING:
|
|
301
308
|
return result->value.string_value && result->value.string_value[0] != '\0';
|
|
302
|
-
case XPATH_RESULT_NODESET:
|
|
303
|
-
|
|
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
|
+
}
|
|
304
333
|
default:
|
|
305
334
|
return 0;
|
|
306
335
|
}
|