leptris 1.9.237.1 → 1.9.239.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 +12 -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 +33 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +95 -11
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +10 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +1 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +1 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +39 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +32 -2
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +15 -2
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +70 -0
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +7 -0
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +5 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +2 -6
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +81 -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: a9988358313da5babf4756d5a3393ff7a805eaabb0a6c04a240bf75173f8248b
|
|
4
|
+
data.tar.gz: 80ddb8c442105e7afad9a1cc40e010f670e115f61e295978157182889a6f2c6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 194a8513e590353c118ff58cac0700789337ce254d8b138e5818a228134f75a5d40f2e4a54cbd8045900182bdb25ea8ca706efe5731e6ae0b3b086d16842f59a
|
|
7
|
+
data.tar.gz: 887dbf39367b2cf2397a493c59d2bb569212acefd4a11f8c1bcd5cbc167640d254e03f4099a71b11c3a9c5f19dcf7c1c75bc4831b6aee1b2533c3cf1f9c3a790
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.239.0] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.239: XML 1.0 §2.11 end-of-line
|
|
13
|
+
normalization (#326) — parsed character data normalizes literal
|
|
14
|
+
CRLF and lone CR to LF before reference expansion (a CR character
|
|
15
|
+
reference stays CR), and attribute values collapse CRLF to ONE
|
|
16
|
+
space per §3.3.3. Nokogiri/libxml2 parity restored for any text
|
|
17
|
+
that ever carried \r\n. CDATA is not normalized. The
|
|
18
|
+
interleaved-lane hardening lands in the next engine sync.
|
|
19
|
+
|
|
8
20
|
## [1.9.237.0] - 2026-09-24
|
|
9
21
|
|
|
10
22
|
### Added
|
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.239"
|
|
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,38 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.239] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- xquery: shortest round-trip E notation for the XQuery number
|
|
8
|
+
spelling. Scientific values print the fewest mantissa digits that
|
|
9
|
+
read back as the same value — at float precision for xs:float
|
|
10
|
+
members (`3.4028235E38`, `2E6`, `4.9E-324`,
|
|
11
|
+
`1.7976931348623157E308`) — in canonical shape: E separator,
|
|
12
|
+
exponent sign only for negatives, no trailing zeros. The spelling
|
|
13
|
+
rides NUMBER results that escape an XQuery evaluation; the XPath
|
|
14
|
+
1.0 surface keeps libxml2 xmlXPathFormatNumber parity.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- QT3 corpus: fn:reverse 57→59, fn:distinct-values 87→89 adopted
|
|
19
|
+
(the dbl1args/flt1args E-notation pins).
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## [1.9.238] - 2026-09-24
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- xquery: computed `processing-instruction NAME { content }` and
|
|
28
|
+
`comment { content }` constructors. In the string-level
|
|
29
|
+
constructor model they serialize to their XML markup
|
|
30
|
+
(`<?NAME data?>` — empty content drops the data and the separating
|
|
31
|
+
space — and `<!--content-->`). The comment guard keys on the
|
|
32
|
+
brace, so the `comment()` node test stays intact.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
3
36
|
## [1.9.237] - 2026-09-24
|
|
4
37
|
|
|
5
38
|
<!-- Edit this section with the actual release notes. -->
|
|
@@ -346,6 +346,8 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
346
346
|
char* val, size_t val_len,
|
|
347
347
|
int has_amp, int has_ws,
|
|
348
348
|
const char* name_colon) {
|
|
349
|
+
/* Live value length — the §2.11 collapse (#326) shrinks it. */
|
|
350
|
+
size_t vlen = val_len;
|
|
349
351
|
/* Attribute-value normalization (XML 1.0 §3.3.3, issue #576):
|
|
350
352
|
* each literal tab/LF/CR in a CDATA attribute becomes a single
|
|
351
353
|
* space. Character references (	) are ASCII text here —
|
|
@@ -355,13 +357,21 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
355
357
|
if (has_ws) {
|
|
356
358
|
char* norm = (char*)leptris_pool_alloc(p->pool, val_len + 1);
|
|
357
359
|
if (!norm) return -1;
|
|
360
|
+
/* §2.11 (#326): CRLF is ONE line break — it collapses to a
|
|
361
|
+
* single space (libxml2/pugixml parity) before the §3.3.3
|
|
362
|
+
* break-to-space mapping; lone CR likewise. */
|
|
363
|
+
size_t wl = 0;
|
|
358
364
|
for (size_t i = 0; i < val_len; i++) {
|
|
359
365
|
char ch = val[i];
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
if (ch == '\r' && i + 1 < val_len && val[i + 1] == '\n') {
|
|
367
|
+
i++;
|
|
368
|
+
}
|
|
369
|
+
norm[wl++] = (ch == '\t' || ch == '\n' || ch == '\r')
|
|
370
|
+
? ' ' : ch;
|
|
362
371
|
}
|
|
363
|
-
norm[
|
|
372
|
+
norm[wl] = '\0';
|
|
364
373
|
val = norm;
|
|
374
|
+
vlen = wl;
|
|
365
375
|
}
|
|
366
376
|
struct leptris_attribute* attr = p->attr_cursor;
|
|
367
377
|
if (DP_UNLIKELY(attr >= p->attr_end)) {
|
|
@@ -373,7 +383,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
373
383
|
}
|
|
374
384
|
|
|
375
385
|
attr->name_view = leptris_sv_from_ptr(name, name_len);
|
|
376
|
-
leptris_attr_value_set_heap(attr, leptris_sv_from_ptr(val,
|
|
386
|
+
leptris_attr_value_set_heap(attr, leptris_sv_from_ptr(val, vlen));
|
|
377
387
|
/* #1125: the name NUL (at name_end) and the value NUL (at the
|
|
378
388
|
* closing quote) get restored after parse — both views must
|
|
379
389
|
* materialize. The normalized/entity-expanded replacements below
|
|
@@ -387,9 +397,9 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
|
|
|
387
397
|
* predefined entities lazily on first read.
|
|
388
398
|
* - No '&': nothing to do. */
|
|
389
399
|
unsigned ent = 0;
|
|
390
|
-
if (
|
|
400
|
+
if (vlen > 0 && has_amp) {
|
|
391
401
|
if (p->dtd) {
|
|
392
|
-
LeptrisStringView dsv = leptris_sv_from_ptr(val,
|
|
402
|
+
LeptrisStringView dsv = leptris_sv_from_ptr(val, vlen);
|
|
393
403
|
char* expanded = leptris_decode_entities_view_with_dtd(
|
|
394
404
|
&dsv, p->dtd, p->pool);
|
|
395
405
|
if (expanded) {
|
|
@@ -1018,6 +1028,46 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
1018
1028
|
return tn;
|
|
1019
1029
|
}
|
|
1020
1030
|
|
|
1031
|
+
/* §2.11 End-of-Line Handling (#326): collapse literal CR — CRLF →
|
|
1032
|
+
* LF, lone CR → LF — into a pool-owned NUL-terminated copy. Literal
|
|
1033
|
+
* bytes only: character references (
) are ASCII text at this
|
|
1034
|
+
* layer and must survive as CR (char refs sit outside §2.11
|
|
1035
|
+
* normalization). out_len receives the collapsed length. NULL on
|
|
1036
|
+
* allocation failure. */
|
|
1037
|
+
static char* dp_eol_collapse(LeptrisMemoryPool* pool, const char* s,
|
|
1038
|
+
size_t len, size_t* out_len) {
|
|
1039
|
+
char* conv = (char*)leptris_pool_alloc(pool, len + 1);
|
|
1040
|
+
if (!conv) return NULL;
|
|
1041
|
+
size_t wl = 0;
|
|
1042
|
+
for (size_t i = 0; i < len; i++) {
|
|
1043
|
+
char ch = s[i];
|
|
1044
|
+
if (ch == '\r') {
|
|
1045
|
+
conv[wl++] = '\n';
|
|
1046
|
+
if (i + 1 < len && s[i + 1] == '\n') i++;
|
|
1047
|
+
} else {
|
|
1048
|
+
conv[wl++] = ch;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
conv[wl] = '\0';
|
|
1052
|
+
*out_len = wl;
|
|
1053
|
+
return conv;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/* §2.11 (#326) text mint for the keep-entity-refs segment paths:
|
|
1057
|
+
* zero-copy when the segment is CR-free, else a pool-owned
|
|
1058
|
+
* collapsed copy (leptris_text_create — ownership differs from the
|
|
1059
|
+
* borrowed carve; callers only stamp line/frozen + wire, both
|
|
1060
|
+
* ownership-agnostic). */
|
|
1061
|
+
static inline LeptrisTextNode* dp_text_create_norm(DParser* p,
|
|
1062
|
+
const char* s,
|
|
1063
|
+
size_t len) {
|
|
1064
|
+
if (memchr(s, '\r', len) == NULL) return dp_text_create(p, s, len);
|
|
1065
|
+
size_t wl;
|
|
1066
|
+
char* conv = dp_eol_collapse(p->pool, s, len, &wl);
|
|
1067
|
+
if (!conv) return NULL;
|
|
1068
|
+
return leptris_text_create(conv, wl, p->pool, p->doc);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1021
1071
|
/* Internal: parse from a writable, NUL-terminated buffer.
|
|
1022
1072
|
* owns_buffer: 1 = document frees buf on leptris_document_free,
|
|
1023
1073
|
* 0 = caller owns buf (in-place mode). */
|
|
@@ -1329,7 +1379,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1329
1379
|
(p.root_chained &&
|
|
1330
1380
|
p.dc_tail == (LeptrisNode*)p.root))) {
|
|
1331
1381
|
LeptrisTextNode* wtn =
|
|
1332
|
-
|
|
1382
|
+
dp_text_create_norm(&p, ws_start, wl);
|
|
1333
1383
|
if (!wtn) goto fail;
|
|
1334
1384
|
wtn->base.frozen = 1;
|
|
1335
1385
|
dp_doc_child(&p, (LeptrisNode*)wtn);
|
|
@@ -1374,6 +1424,10 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1374
1424
|
* entity-free docs. The memchr fallback covers the
|
|
1375
1425
|
* >48-byte tail region only. */
|
|
1376
1426
|
int has_amp = 0;
|
|
1427
|
+
/* §2.11 (#326): literal-CR tracking rides the same fused
|
|
1428
|
+
* loop — CR-bearing runs take the pool-copy collapse,
|
|
1429
|
+
* CR-free runs (the norm) keep the zero-copy carve. */
|
|
1430
|
+
int has_cr = 0;
|
|
1377
1431
|
{
|
|
1378
1432
|
const char* q = p.pos;
|
|
1379
1433
|
const char* probe_end = p.probe_slack
|
|
@@ -1383,12 +1437,14 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1383
1437
|
char ch = *q;
|
|
1384
1438
|
if (ch == '<') { lt = (char*)q; goto text_done; }
|
|
1385
1439
|
has_amp |= (ch == '&');
|
|
1440
|
+
has_cr |= (ch == '\r');
|
|
1386
1441
|
q++;
|
|
1387
1442
|
}
|
|
1388
1443
|
if (q >= p.end) goto text_done;
|
|
1389
1444
|
lt = (char*)memchr(q, '<', p.end - q);
|
|
1390
1445
|
if (!lt) lt = p.end;
|
|
1391
1446
|
has_amp |= memchr(q, '&', (size_t)((char*)lt - q)) != NULL;
|
|
1447
|
+
has_cr |= memchr(q, '\r', (size_t)((char*)lt - q)) != NULL;
|
|
1392
1448
|
}
|
|
1393
1449
|
text_done:
|
|
1394
1450
|
p.pos = lt ? lt : p.end;
|
|
@@ -1447,7 +1503,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1447
1503
|
}
|
|
1448
1504
|
}
|
|
1449
1505
|
if (!rt) {
|
|
1450
|
-
rt =
|
|
1506
|
+
rt = dp_text_create_norm(&p, seg, slen);
|
|
1451
1507
|
if (!rt) goto fail;
|
|
1452
1508
|
/* #1285 slice 4: terminate the segment.
|
|
1453
1509
|
* The '&' is dead: the walker has passed
|
|
@@ -1486,7 +1542,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1486
1542
|
}
|
|
1487
1543
|
}
|
|
1488
1544
|
if (!rt) {
|
|
1489
|
-
rt =
|
|
1545
|
+
rt = dp_text_create_norm(&p, seg, slen);
|
|
1490
1546
|
if (!rt) goto fail;
|
|
1491
1547
|
}
|
|
1492
1548
|
rt->base.line = text_off;
|
|
@@ -1505,17 +1561,34 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1505
1561
|
* raw terminated run. Entity-free runs pay one memchr
|
|
1506
1562
|
* and stay zero-copy. */
|
|
1507
1563
|
LeptrisTextNode* tn;
|
|
1564
|
+
/* §2.11 (#326): collapse literal CR BEFORE entity decode
|
|
1565
|
+
* — 
 expands after normalization and survives as
|
|
1566
|
+
* CR, exactly as the spec orders it. Collapsed runs are
|
|
1567
|
+
* pool-owned copies; CR-free runs keep every existing
|
|
1568
|
+
* path (entity-free stays zero-copy, entity-decode
|
|
1569
|
+
* fallback stays borrowed). */
|
|
1570
|
+
char* collapsed = NULL;
|
|
1571
|
+
size_t clen = tlen;
|
|
1572
|
+
if (has_cr) {
|
|
1573
|
+
collapsed = dp_eol_collapse(pool, text_start, tlen, &clen);
|
|
1574
|
+
if (!collapsed) goto fail;
|
|
1575
|
+
}
|
|
1508
1576
|
if (has_amp) {
|
|
1509
|
-
LeptrisStringView sv = leptris_sv_from_ptr(
|
|
1577
|
+
LeptrisStringView sv = leptris_sv_from_ptr(
|
|
1578
|
+
collapsed ? collapsed : text_start, clen);
|
|
1510
1579
|
char* expanded = p.dtd
|
|
1511
1580
|
? leptris_decode_entities_view_with_dtd(
|
|
1512
1581
|
&sv, p.dtd, pool)
|
|
1513
1582
|
: leptris_decode_entities_view(&sv, pool);
|
|
1514
1583
|
if (expanded) {
|
|
1515
1584
|
tn = leptris_text_create(expanded, strlen(expanded), pool, p.doc);
|
|
1585
|
+
} else if (collapsed) {
|
|
1586
|
+
tn = leptris_text_create(collapsed, clen, pool, p.doc);
|
|
1516
1587
|
} else {
|
|
1517
1588
|
tn = dp_text_create(&p, text_start, tlen);
|
|
1518
1589
|
}
|
|
1590
|
+
} else if (collapsed) {
|
|
1591
|
+
tn = leptris_text_create(collapsed, clen, pool, p.doc);
|
|
1519
1592
|
} else {
|
|
1520
1593
|
tn = dp_text_create(&p, text_start, tlen);
|
|
1521
1594
|
}
|
|
@@ -1773,7 +1846,18 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1773
1846
|
LeptrisCDATANode* cd = (LeptrisCDATANode*)
|
|
1774
1847
|
dp_cpi_carve(&p, LEPTRIS_NODE_TYPE_CDATA);
|
|
1775
1848
|
if (cd) {
|
|
1776
|
-
|
|
1849
|
+
/* §2.11 (#326): CDATA content is Char data —
|
|
1850
|
+
* literal CR collapses to LF; CR-free content
|
|
1851
|
+
* keeps the zero-copy view. */
|
|
1852
|
+
if (memchr(start, '\r', (size_t)(p.pos - start))) {
|
|
1853
|
+
size_t wl;
|
|
1854
|
+
char* conv = dp_eol_collapse(
|
|
1855
|
+
pool, start, (size_t)(p.pos - start), &wl);
|
|
1856
|
+
if (!conv) goto fail;
|
|
1857
|
+
cd->content = conv;
|
|
1858
|
+
} else {
|
|
1859
|
+
cd->content = start; /* zero-copy, buf-lifetime */
|
|
1860
|
+
}
|
|
1777
1861
|
} else {
|
|
1778
1862
|
cd = leptris_cdata_create(start, p.pos - start, pool);
|
|
1779
1863
|
if (!cd) goto fail;
|
|
@@ -654,6 +654,11 @@ struct leptris_xpath_result {
|
|
|
654
654
|
* NULL for the untyped/string class. NOT owned — static
|
|
655
655
|
* literals only. Cleared in result_new (free-list recycle). */
|
|
656
656
|
const char* atomic_type;
|
|
657
|
+
/* XQuery number spelling: set on NUMBER results that escape an
|
|
658
|
+
* XQuery evaluation, so result_string renders the shortest
|
|
659
|
+
* round-trip E form instead of the libxml2-parity XPath
|
|
660
|
+
* spelling. Cleared in result_new (free-list recycle). */
|
|
661
|
+
int xq_spelling;
|
|
657
662
|
};
|
|
658
663
|
|
|
659
664
|
/* Namespace mapping for XPath context (v0.8.0) */
|
|
@@ -825,6 +830,11 @@ typedef enum {
|
|
|
825
830
|
* joined by '\x01' ("*" catches all), children[0] = try body,
|
|
826
831
|
* children[1..] = catch bodies in order. */
|
|
827
832
|
XPATH_OP_DOCUMENT_CTOR,
|
|
833
|
+
/* processing-instruction NAME { content } and
|
|
834
|
+
* comment { content } — the string-level ctor model serializes
|
|
835
|
+
* to the XML markup form (QT3 cbcl-deep-equal-002..004). */
|
|
836
|
+
XPATH_OP_PI_CTOR,
|
|
837
|
+
XPATH_OP_COMMENT_CTOR,
|
|
828
838
|
XPATH_OP_TRY,
|
|
829
839
|
/* XQuery 3.0 typeswitch: value = case SequenceTypes joined
|
|
830
840
|
* by '\x01' (a trailing empty entry = the default arm),
|
|
@@ -743,6 +743,7 @@ struct leptris_xpath_result* xpath_result_new(XPathResultType type) {
|
|
|
743
743
|
result->type = type;
|
|
744
744
|
result->is_int = 0; /* free-list recycling would leak it */
|
|
745
745
|
result->atomic_type = NULL;
|
|
746
|
+
result->xq_spelling = 0;
|
|
746
747
|
|
|
747
748
|
/* Initialize union based on type. XPATH_RESULT_CACHED is the
|
|
748
749
|
* free-list sentinel — a fresh result never carries it. */
|
|
@@ -21,6 +21,7 @@ char* xpath_to_string(struct leptris_xpath_result* result);
|
|
|
21
21
|
char* get_node_text(void* node);
|
|
22
22
|
char* xpath_number_to_string(double number);
|
|
23
23
|
char* xpath_number_to_string_xq(double number);
|
|
24
|
+
char* xpath_number_to_string_xq_typed(double number, int float_prec);
|
|
24
25
|
char* xpath_int_to_string(long long v);
|
|
25
26
|
|
|
26
27
|
/* From evaluator_operators.c — synthetic sequence members and the
|
|
@@ -1521,6 +1521,45 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
|
|
|
1521
1521
|
* escape &<"', text content escapes &<; raw expression content
|
|
1522
1522
|
* passes through (a nested constructor's result is already
|
|
1523
1523
|
* markup; arbitrary-string escaping is a value-model limit). */
|
|
1524
|
+
/* Computed PI/comment constructors (string-level ctor model):
|
|
1525
|
+
* serialize to the XML markup form. PI data is raw (no
|
|
1526
|
+
* entity-escaping in the data position); empty content drops
|
|
1527
|
+
* the data and the separating space. */
|
|
1528
|
+
if (op == XPATH_OP_PI_CTOR || op == XPATH_OP_COMMENT_CTOR) {
|
|
1529
|
+
char* s = NULL;
|
|
1530
|
+
if (ast->child_count >= 1) {
|
|
1531
|
+
struct leptris_xpath_result* v =
|
|
1532
|
+
evaluate_expr(ctx, ast->children[0]);
|
|
1533
|
+
if (v) s = xpath_to_string(v);
|
|
1534
|
+
xpath_result_free(v);
|
|
1535
|
+
}
|
|
1536
|
+
if (!s) s = leptris_strdup("");
|
|
1537
|
+
struct leptris_xpath_result* out =
|
|
1538
|
+
xpath_result_new(XPATH_RESULT_STRING);
|
|
1539
|
+
if (!out) {
|
|
1540
|
+
free(s);
|
|
1541
|
+
return NULL;
|
|
1542
|
+
}
|
|
1543
|
+
const char* target =
|
|
1544
|
+
op == XPATH_OP_PI_CTOR && ast->value ? ast->value : "";
|
|
1545
|
+
size_t tl = strlen(target);
|
|
1546
|
+
size_t dl = strlen(s);
|
|
1547
|
+
char* buf = (char*)malloc(tl + dl + 8);
|
|
1548
|
+
if (!buf) {
|
|
1549
|
+
free(s);
|
|
1550
|
+
xpath_result_free(out);
|
|
1551
|
+
return NULL;
|
|
1552
|
+
}
|
|
1553
|
+
if (op == XPATH_OP_PI_CTOR)
|
|
1554
|
+
snprintf(buf, tl + dl + 8, "<?%s%s%s?>", target,
|
|
1555
|
+
dl ? " " : "", s);
|
|
1556
|
+
else
|
|
1557
|
+
snprintf(buf, dl + 8, "<!--%s-->", s);
|
|
1558
|
+
out->value.string_value = buf;
|
|
1559
|
+
free(s);
|
|
1560
|
+
return out;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1524
1563
|
if (op == XPATH_OP_TEXT_CTOR || op == XPATH_OP_ATTRIBUTE_CTOR ||
|
|
1525
1564
|
op == XPATH_OP_ELEMENT_CTOR) {
|
|
1526
1565
|
if (op != XPATH_OP_ELEMENT_CTOR) {
|
|
@@ -88,6 +88,13 @@ char* xpath_number_to_string(double number) {
|
|
|
88
88
|
* XsltNumberFormat.MatchesLibxml2 and the libxslt suite), so this
|
|
89
89
|
* spelling is only used where the context sets xquery_spelling. */
|
|
90
90
|
char* xpath_number_to_string_xq(double number) {
|
|
91
|
+
return xpath_number_to_string_xq_typed(number, 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* float_prec: the value is a float32-widened double — the shortest
|
|
95
|
+
* round-trip loop reads back at float precision so xs:float spells
|
|
96
|
+
* "3.4028235E38", not the widened-double digits. */
|
|
97
|
+
char* xpath_number_to_string_xq_typed(double number, int float_prec) {
|
|
91
98
|
if (isnan(number)) return leptris_strdup("NaN");
|
|
92
99
|
if (isinf(number))
|
|
93
100
|
return leptris_strdup(number > 0 ? "INF" : "-INF");
|
|
@@ -103,8 +110,31 @@ char* xpath_number_to_string_xq(double number) {
|
|
|
103
110
|
int size;
|
|
104
111
|
double absolute_value = fabs(number);
|
|
105
112
|
if (absolute_value > 1e18 || absolute_value < 1e-18) {
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
/* Shortest round-trip scientific spelling (Saxon/QT3): the
|
|
114
|
+
* fewest mantissa digits that read back as the same double,
|
|
115
|
+
* in canonical form — E separator, exponent sign only for
|
|
116
|
+
* negatives, no trailing zeros ("1.7976931348623157E308",
|
|
117
|
+
* "2E6", "4.9E-324"). Returns directly: the shared trim
|
|
118
|
+
* below must not eat exponent digits. */
|
|
119
|
+
char mant[64];
|
|
120
|
+
int prec;
|
|
121
|
+
for (prec = 1; prec <= 17; prec++) {
|
|
122
|
+
snprintf(mant, sizeof(mant), "%.*e", prec, number);
|
|
123
|
+
double back = strtod(mant, NULL);
|
|
124
|
+
if (float_prec) back = (double)((float)back);
|
|
125
|
+
if (back == number) break;
|
|
126
|
+
}
|
|
127
|
+
char* e = strchr(mant, 'e');
|
|
128
|
+
if (!e) return leptris_strdup(mant);
|
|
129
|
+
*e = 0;
|
|
130
|
+
char* last = mant + strlen(mant);
|
|
131
|
+
while (last > mant && *(last - 1) == '0') last--;
|
|
132
|
+
if (last > mant && *(last - 1) == '.') last--;
|
|
133
|
+
*last = 0;
|
|
134
|
+
const char* exp = e + 1;
|
|
135
|
+
if (*exp == '+') exp++;
|
|
136
|
+
snprintf(work, sizeof(work), "%sE%s", mant, exp);
|
|
137
|
+
return leptris_strdup(work);
|
|
108
138
|
} else {
|
|
109
139
|
/* Fraction budget: integer digits first, the fraction spends
|
|
110
140
|
* the remaining significant digits (33 places floor for the
|
|
@@ -160,7 +160,15 @@ static char** collect_items_raw(XPathContext* ctx, XPathASTNode** args,
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
} else if (r->type == XPATH_RESULT_NUMBER) {
|
|
163
|
-
|
|
163
|
+
/* XQuery-spelled doubles (shortest round-trip E form) when
|
|
164
|
+
* the eval is XQuery-flavored — same rule as the sequence
|
|
165
|
+
* operator's scalar spread. */
|
|
166
|
+
char* s = (ctx->xquery_spelling && !r->is_int)
|
|
167
|
+
? xpath_number_to_string_xq_typed(
|
|
168
|
+
r->value.number_value,
|
|
169
|
+
r->atomic_type &&
|
|
170
|
+
strcmp(r->atomic_type, "xs:float") == 0)
|
|
171
|
+
: scalar_str(r);
|
|
164
172
|
size_t sl = s ? strlen(s) : 0;
|
|
165
173
|
items = (char**)malloc(sizeof(char*));
|
|
166
174
|
if (!items) { free(s); xpath_result_free(r); return NULL; }
|
|
@@ -441,7 +449,12 @@ static struct leptris_xpath_result* fn_index_of(XPathContext* ctx,
|
|
|
441
449
|
needle = leptris_strdup("");
|
|
442
450
|
}
|
|
443
451
|
} else if (v && v->type == XPATH_RESULT_NUMBER) {
|
|
444
|
-
char* s =
|
|
452
|
+
char* s = (ctx->xquery_spelling && !v->is_int)
|
|
453
|
+
? xpath_number_to_string_xq_typed(
|
|
454
|
+
v->value.number_value,
|
|
455
|
+
v->atomic_type &&
|
|
456
|
+
strcmp(v->atomic_type, "xs:float") == 0)
|
|
457
|
+
: scalar_str(v);
|
|
445
458
|
size_t sl = s ? strlen(s) : 0;
|
|
446
459
|
needle = (char*)malloc(sl + 3);
|
|
447
460
|
if (needle) {
|
|
@@ -1453,6 +1453,76 @@ static XPathASTNode* parse_path_expr(XPathParser* parser) {
|
|
|
1453
1453
|
tc->value = leptris_strdup(tests);
|
|
1454
1454
|
return parse_postfix_ops(parser, tc);
|
|
1455
1455
|
}
|
|
1456
|
+
/* processing-instruction NAME { content } — the computed
|
|
1457
|
+
* PI constructor. The target is the NCNAME/QNAME; the
|
|
1458
|
+
* optional content serializes as the PI data. */
|
|
1459
|
+
else if (current_token(parser)->type == TOK_NCNAME &&
|
|
1460
|
+
current_token(parser)->value_len == 22 &&
|
|
1461
|
+
memcmp(current_token(parser)->value,
|
|
1462
|
+
"processing-instruction", 22) == 0 &&
|
|
1463
|
+
next && (next->type == TOK_NCNAME ||
|
|
1464
|
+
next->type == TOK_QNAME) &&
|
|
1465
|
+
parser->token_pos + 2 < parser->token_count &&
|
|
1466
|
+
parser->tokens[parser->token_pos + 2].type ==
|
|
1467
|
+
TOK_LBRACE) {
|
|
1468
|
+
char name[128];
|
|
1469
|
+
size_t nl = next->value_len;
|
|
1470
|
+
if (nl >= sizeof(name)) return NULL;
|
|
1471
|
+
memcpy(name, next->value, nl);
|
|
1472
|
+
name[nl] = 0;
|
|
1473
|
+
advance_token(parser);
|
|
1474
|
+
advance_token(parser);
|
|
1475
|
+
advance_token(parser);
|
|
1476
|
+
XPathASTNode* pc = ast_node_new(XPATH_AST_OPERATOR);
|
|
1477
|
+
if (!pc) return NULL;
|
|
1478
|
+
pc->number_value = (double)XPATH_OP_PI_CTOR;
|
|
1479
|
+
pc->value = leptris_strdup(name);
|
|
1480
|
+
if (current_token_is(parser, TOK_RBRACE)) {
|
|
1481
|
+
XPathASTNode* empty = ast_node_new(XPATH_AST_OPERATOR);
|
|
1482
|
+
if (!empty) { ast_node_free(pc); return NULL; }
|
|
1483
|
+
empty->number_value = (double)XPATH_OP_SEQUENCE;
|
|
1484
|
+
ast_node_add_child(pc, empty);
|
|
1485
|
+
} else {
|
|
1486
|
+
XPathASTNode* item = parse_expr(parser);
|
|
1487
|
+
if (!item) { ast_node_free(pc); return NULL; }
|
|
1488
|
+
ast_node_add_child(pc, item);
|
|
1489
|
+
}
|
|
1490
|
+
if (!current_token_is(parser, TOK_RBRACE)) {
|
|
1491
|
+
ast_node_free(pc);
|
|
1492
|
+
return NULL;
|
|
1493
|
+
}
|
|
1494
|
+
advance_token(parser);
|
|
1495
|
+
return parse_postfix_ops(parser, pc);
|
|
1496
|
+
}
|
|
1497
|
+
/* comment { content } — the computed comment constructor.
|
|
1498
|
+
* Guarded by the brace so the comment() node test stays
|
|
1499
|
+
* intact. */
|
|
1500
|
+
else if (current_token(parser)->type == TOK_NCNAME &&
|
|
1501
|
+
next && next->type == TOK_LBRACE &&
|
|
1502
|
+
current_token(parser)->value_len == 7 &&
|
|
1503
|
+
memcmp(current_token(parser)->value, "comment", 7) == 0) {
|
|
1504
|
+
advance_token(parser);
|
|
1505
|
+
advance_token(parser);
|
|
1506
|
+
XPathASTNode* cc = ast_node_new(XPATH_AST_OPERATOR);
|
|
1507
|
+
if (!cc) return NULL;
|
|
1508
|
+
cc->number_value = (double)XPATH_OP_COMMENT_CTOR;
|
|
1509
|
+
if (current_token_is(parser, TOK_RBRACE)) {
|
|
1510
|
+
XPathASTNode* empty = ast_node_new(XPATH_AST_OPERATOR);
|
|
1511
|
+
if (!empty) { ast_node_free(cc); return NULL; }
|
|
1512
|
+
empty->number_value = (double)XPATH_OP_SEQUENCE;
|
|
1513
|
+
ast_node_add_child(cc, empty);
|
|
1514
|
+
} else {
|
|
1515
|
+
XPathASTNode* item = parse_expr(parser);
|
|
1516
|
+
if (!item) { ast_node_free(cc); return NULL; }
|
|
1517
|
+
ast_node_add_child(cc, item);
|
|
1518
|
+
}
|
|
1519
|
+
if (!current_token_is(parser, TOK_RBRACE)) {
|
|
1520
|
+
ast_node_free(cc);
|
|
1521
|
+
return NULL;
|
|
1522
|
+
}
|
|
1523
|
+
advance_token(parser);
|
|
1524
|
+
return parse_postfix_ops(parser, cc);
|
|
1525
|
+
}
|
|
1456
1526
|
else if (current_token(parser)->type == TOK_NCNAME &&
|
|
1457
1527
|
next && next->type == TOK_LBRACE &&
|
|
1458
1528
|
current_token(parser)->value_len == 4 &&
|
|
@@ -486,6 +486,13 @@ LEPTRIS_API char* leptris_xpath_result_string(LeptrisXPathResult result) {
|
|
|
486
486
|
case XPATH_RESULT_BOOLEAN:
|
|
487
487
|
return leptris_strdup(result->value.boolean_value ? "true" : "false");
|
|
488
488
|
case XPATH_RESULT_NUMBER:
|
|
489
|
+
if (result->xq_spelling) {
|
|
490
|
+
char* s = xpath_number_to_string_xq_typed(
|
|
491
|
+
result->value.number_value,
|
|
492
|
+
result->atomic_type &&
|
|
493
|
+
strcmp(result->atomic_type, "xs:float") == 0);
|
|
494
|
+
if (s) return s;
|
|
495
|
+
}
|
|
489
496
|
if (result->is_int) return xpath_int_to_string(result->int_value);
|
|
490
497
|
return xpath_number_to_string(result->value.number_value);
|
|
491
498
|
case XPATH_RESULT_NODESET:
|
|
@@ -3168,6 +3168,11 @@ static LeptrisXPathResult xq_eval_impl(
|
|
|
3168
3168
|
else
|
|
3169
3169
|
free(s);
|
|
3170
3170
|
}
|
|
3171
|
+
/* XQuery number spelling rides the result: NUMBER returns render
|
|
3172
|
+
* via the shortest round-trip E form in result_string (the
|
|
3173
|
+
* libxml2-parity XPath printer stays for the XPath surface). */
|
|
3174
|
+
if (result && result->type == XPATH_RESULT_NUMBER && !result->is_int)
|
|
3175
|
+
result->xq_spelling = 1;
|
|
3171
3176
|
return result;
|
|
3172
3177
|
}
|
|
3173
3178
|
|
|
@@ -922,12 +922,10 @@ TEST(Qt3Subset, FnRemove) {
|
|
|
922
922
|
|
|
923
923
|
TEST(Qt3Subset, FnReverse) {
|
|
924
924
|
/* Reds are the typed-atom class (NaN/INF/boolean member compare). */
|
|
925
|
-
run_test_set("fn/reverse.xml", {},
|
|
925
|
+
run_test_set("fn/reverse.xml", {}, 59, {},
|
|
926
926
|
{
|
|
927
927
|
"K2-SeqReverseFunc-1",
|
|
928
928
|
"K2-SeqReverseFunc-2",
|
|
929
|
-
"fn-reversedbl1args-1",
|
|
930
|
-
"fn-reversedbl1args-3",
|
|
931
929
|
|
|
932
930
|
});
|
|
933
931
|
}
|
|
@@ -961,7 +959,7 @@ TEST(Qt3Subset, FnDistinctValues) {
|
|
|
961
959
|
/* mixed-args-012 (decimal-vs-float dedup) diverges ONLY inside
|
|
962
960
|
* the LTO'd test binary — identical query passes standalone and
|
|
963
961
|
* via the public API; CI's non-LTO/gcc legs are the arbiter. */
|
|
964
|
-
run_test_set("fn/distinct-values.xml", {},
|
|
962
|
+
run_test_set("fn/distinct-values.xml", {}, 89, {},
|
|
965
963
|
{
|
|
966
964
|
"cbcl-distinct-values-002",
|
|
967
965
|
"cbcl-distinct-values-002b",
|
|
@@ -975,8 +973,6 @@ TEST(Qt3Subset, FnDistinctValues) {
|
|
|
975
973
|
"fn-distinct-values-mixed-args-010",
|
|
976
974
|
"fn-distinct-values-mixed-args-012",
|
|
977
975
|
"fn-distinct-values-mixed-args-018",
|
|
978
|
-
"fn-distinct-valuesdbl1args-1",
|
|
979
|
-
"fn-distinct-valuesdbl1args-3",
|
|
980
976
|
|
|
981
977
|
});
|
|
982
978
|
}
|
|
@@ -821,3 +821,84 @@ TEST(XQueryLeaks, RepeatedFunctionDeclarationEvalsDoNotLeak) {
|
|
|
821
821
|
}
|
|
822
822
|
leptris_document_free(doc);
|
|
823
823
|
}
|
|
824
|
+
|
|
825
|
+
/* Computed PI and comment constructors (QT3 cbcl-deep-equal-002..004
|
|
826
|
+
* class): the string-level ctor model serializes them to their XML
|
|
827
|
+
* markup, so deep-equal compares target+data / comment text. */
|
|
828
|
+
TEST(XQueryCtors, PIConstructor) {
|
|
829
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
830
|
+
LeptrisDocument doc = leptris_parse_string("<e/>", 4, &st);
|
|
831
|
+
ASSERT_NE(doc, nullptr);
|
|
832
|
+
struct {
|
|
833
|
+
const char* q;
|
|
834
|
+
const char* want;
|
|
835
|
+
} cases[] = {
|
|
836
|
+
{"processing-instruction target { \"data\" }", "<?target data?>"},
|
|
837
|
+
{"processing-instruction target { \"\" }", "<?target?>"},
|
|
838
|
+
{"processing-instruction target { () }", "<?target?>"},
|
|
839
|
+
};
|
|
840
|
+
for (auto& c : cases) {
|
|
841
|
+
LeptrisXQuery xq = leptris_xquery_parse(c.q, strlen(c.q));
|
|
842
|
+
ASSERT_NE(xq, nullptr) << c.q;
|
|
843
|
+
LeptrisXPathResult r = leptris_xquery_eval(xq, doc, NULL);
|
|
844
|
+
ASSERT_NE(r, nullptr) << c.q;
|
|
845
|
+
char* s = leptris_xpath_result_string(r);
|
|
846
|
+
ASSERT_NE(s, nullptr) << c.q;
|
|
847
|
+
EXPECT_STREQ(s, c.want) << c.q;
|
|
848
|
+
leptris_free_string(s);
|
|
849
|
+
leptris_xpath_result_free(r);
|
|
850
|
+
leptris_xquery_free(xq);
|
|
851
|
+
}
|
|
852
|
+
leptris_document_free(doc);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
TEST(XQueryCtors, CommentConstructor) {
|
|
856
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
857
|
+
LeptrisDocument doc = leptris_parse_string("<e/>", 4, &st);
|
|
858
|
+
ASSERT_NE(doc, nullptr);
|
|
859
|
+
const char* cq = "comment { \"hello\" }";
|
|
860
|
+
LeptrisXQuery xq = leptris_xquery_parse(cq, strlen(cq));
|
|
861
|
+
ASSERT_NE(xq, nullptr);
|
|
862
|
+
LeptrisXPathResult r = leptris_xquery_eval(xq, doc, NULL);
|
|
863
|
+
ASSERT_NE(r, nullptr);
|
|
864
|
+
char* s = leptris_xpath_result_string(r);
|
|
865
|
+
ASSERT_NE(s, nullptr);
|
|
866
|
+
EXPECT_STREQ(s, "<!--hello-->");
|
|
867
|
+
leptris_free_string(s);
|
|
868
|
+
leptris_xpath_result_free(r);
|
|
869
|
+
leptris_xquery_free(xq);
|
|
870
|
+
leptris_document_free(doc);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
TEST(XQueryCtors, PICtorDeepEqualByTargetAndData) {
|
|
874
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
875
|
+
LeptrisDocument doc = leptris_parse_string("<e/>", 4, &st);
|
|
876
|
+
ASSERT_NE(doc, nullptr);
|
|
877
|
+
struct {
|
|
878
|
+
const char* q;
|
|
879
|
+
bool want;
|
|
880
|
+
} cases[] = {
|
|
881
|
+
{"deep-equal(processing-instruction cheese { \"brie\" }, "
|
|
882
|
+
"processing-instruction cheese { \"brie\" })",
|
|
883
|
+
true},
|
|
884
|
+
{"deep-equal(processing-instruction cheese { \"brie\" }, "
|
|
885
|
+
"processing-instruction cheese { \"stilton\" })",
|
|
886
|
+
false},
|
|
887
|
+
{"deep-equal(processing-instruction foo { \"bar\" }, "
|
|
888
|
+
"processing-instruction bar { \"foo\" })",
|
|
889
|
+
false},
|
|
890
|
+
};
|
|
891
|
+
for (auto& c : cases) {
|
|
892
|
+
LeptrisXQuery xq = leptris_xquery_parse(c.q, strlen(c.q));
|
|
893
|
+
ASSERT_NE(xq, nullptr) << c.q;
|
|
894
|
+
LeptrisXPathResult r = leptris_xquery_eval(xq, doc, NULL);
|
|
895
|
+
ASSERT_NE(r, nullptr) << c.q;
|
|
896
|
+
char* s = leptris_xpath_result_string(r);
|
|
897
|
+
ASSERT_NE(s, nullptr) << c.q;
|
|
898
|
+
EXPECT_STREQ(s, c.want ? "true" : "false") << c.q;
|
|
899
|
+
leptris_free_string(s);
|
|
900
|
+
leptris_xpath_result_free(r);
|
|
901
|
+
leptris_xquery_free(xq);
|
|
902
|
+
}
|
|
903
|
+
leptris_document_free(doc);
|
|
904
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.239",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.239.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|