leptris 1.9.318.0-arm-linux → 1.9.319.0-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 +30 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +9 -0
- data/lib/leptris/xml/xsd.rb +32 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +34 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/src/include/leptris.h +11 -0
- data/vendor-src/libleptris/src/leptris/rng/rng_internal.h +8 -0
- data/vendor-src/libleptris/src/leptris/rng/rng_regex.c +4 -0
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +153 -2
- data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +445 -0
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +43 -0
- data/vendor-src/libleptris/test/rng/test_rng.cpp +15 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +112 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d73bfbca7f678b33985e74690f98802f0f020a438cb7f248df62b4531c3f33b
|
|
4
|
+
data.tar.gz: 81923f67f5adb8e770fa8d9228677b58f33990babc4261a38766c0186b00654e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 601ca1dfd786e2d15f76693fee886b49a25e336a6f2cbd43365bda6ca5de327239750abdf3114ff44178d276b8bb4f8bd13a2bc7884ddb8d0ed1c3a4858a6bbf
|
|
7
|
+
data.tar.gz: 3939d7264a015ef4ab66a32027896dec0adee34df022eda80afdf3f01f3c9a95a53c1f393bb634e3430f5797e99383055b765c1bf820ab2e3029cae05f87b9fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## [1.9.319.0] - 2026-10-08
|
|
2
|
+
|
|
3
|
+
### Added — engine sync (libleptris 1.9.319)
|
|
4
|
+
|
|
5
|
+
- **XSD lexical validation, slice 2 (#1075)**:
|
|
6
|
+
`XSD.builtin_valid?` covers the tier-1 built-in table (string
|
|
7
|
+
family, token, boolean, decimal/integer ranges, float/double,
|
|
8
|
+
date/time with bounds, duration, hex/base64Binary) by the
|
|
9
|
+
"xs:NAME" reference spelling; `Schema#simple_valid?` resolves
|
|
10
|
+
user simpleTypes through cycle-guarded restriction chains
|
|
11
|
+
(pattern / enumeration / min-max / length / digits facets).
|
|
12
|
+
The C face's -1 (not-in-table / unknown type) raises — a silent
|
|
13
|
+
false would let misspelled types validate nothing forever.
|
|
14
|
+
(Slice 1's compile surface rode in the parallel 1.9.318.0.)
|
|
15
|
+
|
|
16
|
+
### Fixed — engine sync (1.9.319)
|
|
17
|
+
|
|
18
|
+
- **RELAX NG exact `{n}` quantifiers (PR #1583)**: the regex
|
|
19
|
+
engine's no-comma branch never set the hi bound — `[A-Z]{3}`
|
|
20
|
+
matched `ABCD`, so any schema using exact bounds silently
|
|
21
|
+
over-accepted. Pinned through the binding's datatype path
|
|
22
|
+
(accepts ABC, rejects ABCD).
|
|
23
|
+
|
|
24
|
+
### Performance — engine sync (1.9.318)
|
|
25
|
+
|
|
26
|
+
- Per-thread small-span arena recycle (#1436): the tiny-doc
|
|
27
|
+
create/destroy row drops ~15% (183→159 ns/doc quiet-hardware) —
|
|
28
|
+
engine-internal; the binding inherits it on every
|
|
29
|
+
Document.create cycle.
|
|
30
|
+
|
|
1
31
|
## [1.9.317.1] - 2026-10-08
|
|
2
32
|
|
|
3
33
|
### 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.319"
|
|
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
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -1295,6 +1295,15 @@ attach_function :leptris_parse_string,
|
|
|
1295
1295
|
[:leptris_xsd_schema], :size_t
|
|
1296
1296
|
attach_function :leptris_xsd_error,
|
|
1297
1297
|
[:leptris_xsd_schema], UTF8_STRING
|
|
1298
|
+
# Slice 2 (libleptris 1.9.319, #1075): lexical validation.
|
|
1299
|
+
# Built-ins take the "xs:NAME" reference spelling; -1 = not
|
|
1300
|
+
# in the tier-1 table. simple_valid resolves a user
|
|
1301
|
+
# simpleType by local name through cycle-guarded restriction
|
|
1302
|
+
# chains; -1 = unknown type.
|
|
1303
|
+
attach_function :leptris_xsd_builtin_valid,
|
|
1304
|
+
[:string, :string], :int
|
|
1305
|
+
attach_function :leptris_xsd_simple_valid,
|
|
1306
|
+
[:leptris_xsd_schema, :string, :string], :int
|
|
1298
1307
|
|
|
1299
1308
|
attach_function :leptris_rng_parse,
|
|
1300
1309
|
[:string, :size_t, :pointer], :leptris_relaxng
|
data/lib/leptris/xml/xsd.rb
CHANGED
|
@@ -9,6 +9,22 @@ module Leptris::XML::XSD
|
|
|
9
9
|
Schema.compile(schema_xml)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Slice 2 (libleptris 1.9.319, #1075): built-in lexical table.
|
|
13
|
+
# +builtin+ takes the "xs:NAME" reference spelling. Returns
|
|
14
|
+
# true/false; an unknown table name raises — a silent false
|
|
15
|
+
# would let misspelled types validate nothing forever.
|
|
16
|
+
def builtin_valid?(builtin, lexical)
|
|
17
|
+
r = Leptris::XML::FFI.leptris_xsd_builtin_valid(
|
|
18
|
+
builtin.to_s, lexical.to_s)
|
|
19
|
+
case r
|
|
20
|
+
when 1 then true
|
|
21
|
+
when 0 then false
|
|
22
|
+
else
|
|
23
|
+
raise ArgumentError,
|
|
24
|
+
"#{builtin.inspect} is not in the tier-1 built-in table"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
# A compiled XSD schema (libleptris >= 1.9.318, #1075 tier-1):
|
|
13
29
|
# the compilation surface — a declaration census and schema-level
|
|
14
30
|
# error reporting. Validation is a later tier and not exposed yet.
|
|
@@ -57,5 +73,21 @@ module Leptris::XML::XSD
|
|
|
57
73
|
msg = Leptris::XML::FFI.leptris_xsd_error(@handle)
|
|
58
74
|
msg.nil? || msg.empty? ? nil : msg
|
|
59
75
|
end
|
|
76
|
+
|
|
77
|
+
# Slice 2 (libleptris 1.9.319, #1075): lexical validation of
|
|
78
|
+
# +lexical+ against the user simpleType +type_name+ (local
|
|
79
|
+
# name) — restriction chains validate every hop's facets,
|
|
80
|
+
# cycle-guarded. Unknown type names raise (the -1 contract).
|
|
81
|
+
def simple_valid?(type_name, lexical)
|
|
82
|
+
r = Leptris::XML::FFI.leptris_xsd_simple_valid(
|
|
83
|
+
@handle, type_name.to_s, lexical.to_s)
|
|
84
|
+
case r
|
|
85
|
+
when 1 then true
|
|
86
|
+
when 0 then false
|
|
87
|
+
else
|
|
88
|
+
raise ArgumentError,
|
|
89
|
+
"#{type_name.inspect} is not a simpleType in this schema"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
60
92
|
end
|
|
61
93
|
end
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.319] - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **XSD validation, tier 1, slices 1–2 (#1075, PRs #1581/#1583).**
|
|
8
|
+
Slice 1: the `leptris_xsd_compile` surface (xs:schema identity
|
|
9
|
+
under any namespace spelling, top-level declaration enumeration,
|
|
10
|
+
error-carrying failure handles) plus the corpus gate — libxml2's
|
|
11
|
+
own `test/schemas` set (350 files, MIT) vendored at
|
|
12
|
+
`test/xsd/libxml2-cases`, **163/163 fixtures compiling** with a
|
|
13
|
+
pinned monotonic floor. Slice 2: `leptris_xsd_builtin_valid` —
|
|
14
|
+
the tier-1 built-in lexical table (string/Name family, token,
|
|
15
|
+
language, anyURI, QName, boolean, decimal, the integer-derivation
|
|
16
|
+
ranges, float/double, the date/time family with month/day bounds,
|
|
17
|
+
duration, hex/base64Binary) — and `leptris_xsd_simple_valid`:
|
|
18
|
+
named simpleTypes captured at compile (base + pattern /
|
|
19
|
+
enumeration / min-max inclusive-exclusive / length family /
|
|
20
|
+
total-fractionDigits facets), validated through cycle-guarded
|
|
21
|
+
restriction chains where every hop's facets bind.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **Regex engine: `{n}` EXACT quantifiers parsed open-ended
|
|
26
|
+
(PR #1583).** The no-comma branch never set the hi bound, so
|
|
27
|
+
`[A-Z]{3}` matched `ABCD` — any RELAX NG schema using exact
|
|
28
|
+
quantifier bounds silently accepted longer runs. Root fix in the
|
|
29
|
+
quantifier parser plus a never-again spec on the direct engine
|
|
30
|
+
call; `rng_internal.h` gained C++ guards for test-side calls.
|
|
31
|
+
- Amalgamated single-TU build: the XSD datatype statics now carry
|
|
32
|
+
the `xsd_` prefix (collision with the xpath lexer's same-named
|
|
33
|
+
statics — the #1217 class).
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
3
37
|
## [1.9.318] - 2026-10-08
|
|
4
38
|
|
|
5
39
|
### Performance
|
|
@@ -909,6 +909,17 @@ LEPTRIS_API size_t leptris_xsd_declaration_count(LeptrisXsdSchema schema);
|
|
|
909
909
|
/* Last compile error detail (NULL when the handle is valid).
|
|
910
910
|
* Schema-owned; lives until leptris_xsd_free. */
|
|
911
911
|
LEPTRIS_API const char* leptris_xsd_error(LeptrisXsdSchema schema);
|
|
912
|
+
|
|
913
|
+
/* Slice 2 — lexical validation. Built-ins take the "xs:NAME"
|
|
914
|
+
* reference spelling; -1 = not in the tier-1 table. */
|
|
915
|
+
LEPTRIS_API int leptris_xsd_builtin_valid(const char* builtin,
|
|
916
|
+
const char* lexical);
|
|
917
|
+
/* A user simpleType compiled into the schema, by local name:
|
|
918
|
+
* 1 valid, 0 invalid, -1 unknown type. Restriction chains (local
|
|
919
|
+
* bases) validate every hop's facets, cycle-guarded. */
|
|
920
|
+
LEPTRIS_API int leptris_xsd_simple_valid(LeptrisXsdSchema schema,
|
|
921
|
+
const char* type_name,
|
|
922
|
+
const char* lexical);
|
|
912
923
|
/* Validate an instance document against the parsed schema
|
|
913
924
|
* (phase-2 core subset: element/attribute/text/data/value/
|
|
914
925
|
* choice/group/interleave/repeats/ref). Returns 1 valid; on 0,
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
#ifndef LEPTRIS_RNG_INTERNAL_H
|
|
9
9
|
#define LEPTRIS_RNG_INTERNAL_H
|
|
10
10
|
|
|
11
|
+
#ifdef __cplusplus
|
|
12
|
+
extern "C" {
|
|
13
|
+
#endif
|
|
14
|
+
|
|
11
15
|
#include "../include/leptris.h"
|
|
12
16
|
#include "../dom/diag.h"
|
|
13
17
|
#include <stddef.h>
|
|
@@ -109,4 +113,8 @@ int rng_regex_matches(const char* pat, const char* text);
|
|
|
109
113
|
void rng_pattern_free(RngPattern* p);
|
|
110
114
|
void rng_grammar_free(RngGrammar* g);
|
|
111
115
|
|
|
116
|
+
#ifdef __cplusplus
|
|
117
|
+
}
|
|
118
|
+
#endif
|
|
119
|
+
|
|
112
120
|
#endif
|
|
@@ -289,6 +289,10 @@ static RreNode* parse_piece(RreParser* ps) {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
+
/* {n} with no comma is EXACT — h must be l, not the
|
|
293
|
+
* -1 "unbounded" sentinel. The old code left h = -1,
|
|
294
|
+
* so [A-Z]{3} matched ABCD (open-ended {3,}). */
|
|
295
|
+
if (*q != ',' && h == -1) h = l;
|
|
292
296
|
if (*q == '}' && l >= 0 && (h == -1 || h >= l)) {
|
|
293
297
|
lo = l;
|
|
294
298
|
hi = h;
|
|
@@ -8,14 +8,119 @@
|
|
|
8
8
|
#include <string.h>
|
|
9
9
|
|
|
10
10
|
#include "../../include/leptris.h"
|
|
11
|
+
#include "xsd_internal.h"
|
|
11
12
|
|
|
12
13
|
#define XSD_NS "http://www.w3.org/2001/XMLSchema"
|
|
13
14
|
|
|
15
|
+
extern int xsd_simple_valid(const XsdSimple* simple, const char* v);
|
|
16
|
+
|
|
14
17
|
struct leptris_xsd_schema {
|
|
15
18
|
size_t declaration_count;
|
|
19
|
+
XsdSimple* simple_types;
|
|
16
20
|
char* error;
|
|
17
21
|
};
|
|
18
22
|
|
|
23
|
+
static char* xsd_strdup(const char* s) {
|
|
24
|
+
if (!s) return NULL;
|
|
25
|
+
size_t n = strlen(s) + 1;
|
|
26
|
+
char* out = (char*)malloc(n);
|
|
27
|
+
if (out) memcpy(out, s, n);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Capture one xs:simpleType's restriction model (slice 2). */
|
|
32
|
+
static XsdSimple* xsd_capture_simple(LeptrisElement st) {
|
|
33
|
+
const char* name = leptris_element_attribute(st, "name");
|
|
34
|
+
if (!name) return NULL; /* anonymous types ride their element */
|
|
35
|
+
|
|
36
|
+
XsdSimple* simple = (XsdSimple*)calloc(1, sizeof(*simple));
|
|
37
|
+
if (!simple) return NULL;
|
|
38
|
+
simple->name = xsd_strdup(name);
|
|
39
|
+
simple->length = simple->min_length = simple->max_length = -1;
|
|
40
|
+
simple->total_digits = simple->fraction_digits = -1;
|
|
41
|
+
|
|
42
|
+
for (LeptrisNodeRef n =
|
|
43
|
+
leptris_node_first_child(leptris_element_as_node(st));
|
|
44
|
+
n; n = leptris_node_next_sibling(n)) {
|
|
45
|
+
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
46
|
+
continue;
|
|
47
|
+
LeptrisElement e = (LeptrisElement)n;
|
|
48
|
+
const char* local = NULL, *prefix = NULL, *uri = NULL;
|
|
49
|
+
leptris_element_expanded_name(e, &local, &prefix, &uri);
|
|
50
|
+
if (uri && strcmp(uri, XSD_NS) != 0) continue;
|
|
51
|
+
if (!local) continue;
|
|
52
|
+
if (strcmp(local, "restriction") == 0) {
|
|
53
|
+
simple->base =
|
|
54
|
+
xsd_strdup(leptris_element_attribute(e, "base"));
|
|
55
|
+
for (LeptrisNodeRef f = leptris_node_first_child(
|
|
56
|
+
leptris_element_as_node(e));
|
|
57
|
+
f; f = leptris_node_next_sibling(f)) {
|
|
58
|
+
if (leptris_node_get_type(f) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
59
|
+
continue;
|
|
60
|
+
LeptrisElement fe = (LeptrisElement)f;
|
|
61
|
+
const char* fl = NULL, *fp = NULL, *fu = NULL;
|
|
62
|
+
leptris_element_expanded_name(fe, &fl, &fp, &fu);
|
|
63
|
+
if (!fl || (fu && strcmp(fu, XSD_NS) != 0)) continue;
|
|
64
|
+
const char* val =
|
|
65
|
+
leptris_element_attribute(fe, "value");
|
|
66
|
+
if (strcmp(fl, "pattern") == 0)
|
|
67
|
+
simple->pattern = xsd_strdup(val);
|
|
68
|
+
else if (strcmp(fl, "enumeration") == 0) {
|
|
69
|
+
XsdFacetValue* fv =
|
|
70
|
+
(XsdFacetValue*)calloc(1, sizeof(*fv));
|
|
71
|
+
if (fv) {
|
|
72
|
+
fv->text = xsd_strdup(val);
|
|
73
|
+
fv->next = simple->enum_values;
|
|
74
|
+
simple->enum_values = fv;
|
|
75
|
+
simple->enum_count++;
|
|
76
|
+
}
|
|
77
|
+
} else if (strcmp(fl, "minInclusive") == 0)
|
|
78
|
+
simple->min_inclusive = xsd_strdup(val);
|
|
79
|
+
else if (strcmp(fl, "minExclusive") == 0)
|
|
80
|
+
simple->min_exclusive = xsd_strdup(val);
|
|
81
|
+
else if (strcmp(fl, "maxInclusive") == 0)
|
|
82
|
+
simple->max_inclusive = xsd_strdup(val);
|
|
83
|
+
else if (strcmp(fl, "maxExclusive") == 0)
|
|
84
|
+
simple->max_exclusive = xsd_strdup(val);
|
|
85
|
+
else if (strcmp(fl, "length") == 0)
|
|
86
|
+
simple->length = val ? strtol(val, NULL, 10) : -1;
|
|
87
|
+
else if (strcmp(fl, "minLength") == 0)
|
|
88
|
+
simple->min_length = val ? strtol(val, NULL, 10) : -1;
|
|
89
|
+
else if (strcmp(fl, "maxLength") == 0)
|
|
90
|
+
simple->max_length = val ? strtol(val, NULL, 10) : -1;
|
|
91
|
+
else if (strcmp(fl, "totalDigits") == 0)
|
|
92
|
+
simple->total_digits = val ? strtol(val, NULL, 10) : -1;
|
|
93
|
+
else if (strcmp(fl, "fractionDigits") == 0)
|
|
94
|
+
simple->fraction_digits = val ? strtol(val, NULL, 10) : -1;
|
|
95
|
+
else if (strcmp(fl, "whiteSpace") == 0)
|
|
96
|
+
simple->whitespace = xsd_strdup(val);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return simple;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Depth-capped local-base resolution with a cycle guard: the
|
|
105
|
+
* restriction chain walks to its built-in, facets of EVERY hop
|
|
106
|
+
* apply (XSD semantics). */
|
|
107
|
+
static const XsdSimple* xsd_find_simple(struct leptris_xsd_schema* s,
|
|
108
|
+
const char* name) {
|
|
109
|
+
for (XsdSimple* t = s->simple_types; t; t = t->next)
|
|
110
|
+
if (strcmp(t->name, name) == 0) return t;
|
|
111
|
+
return NULL;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static int xsd_valid_chain(struct leptris_xsd_schema* s,
|
|
115
|
+
const XsdSimple* t, const char* v, int depth) {
|
|
116
|
+
if (!t || depth > 32) return 0;
|
|
117
|
+
if (t->base && strncmp(t->base, "xs:", 3) != 0) {
|
|
118
|
+
const XsdSimple* next = xsd_find_simple(s, t->base);
|
|
119
|
+
if (next && !xsd_valid_chain(s, next, v, depth + 1)) return 0;
|
|
120
|
+
}
|
|
121
|
+
return xsd_simple_valid(t, v);
|
|
122
|
+
}
|
|
123
|
+
|
|
19
124
|
static void xsd_set_error(struct leptris_xsd_schema* s, const char* msg) {
|
|
20
125
|
free(s->error);
|
|
21
126
|
s->error = NULL;
|
|
@@ -88,8 +193,15 @@ LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
|
|
|
88
193
|
continue;
|
|
89
194
|
leptris_element_expanded_name((LeptrisElement)n, &local, &prefix,
|
|
90
195
|
&uri);
|
|
91
|
-
if (xsd_is_declaration(local, uri))
|
|
92
|
-
|
|
196
|
+
if (!xsd_is_declaration(local, uri)) continue;
|
|
197
|
+
s->declaration_count++;
|
|
198
|
+
if (strcmp(local, "simpleType") == 0 && uri && !strcmp(uri, XSD_NS)) {
|
|
199
|
+
XsdSimple* captured = xsd_capture_simple((LeptrisElement)n);
|
|
200
|
+
if (captured) {
|
|
201
|
+
captured->next = s->simple_types;
|
|
202
|
+
s->simple_types = captured;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
93
205
|
}
|
|
94
206
|
|
|
95
207
|
leptris_document_free(doc);
|
|
@@ -106,10 +218,49 @@ fail:
|
|
|
106
218
|
LEPTRIS_API void leptris_xsd_free(LeptrisXsdSchema schema) {
|
|
107
219
|
struct leptris_xsd_schema* s = (struct leptris_xsd_schema*)schema;
|
|
108
220
|
if (!s) return;
|
|
221
|
+
XsdSimple* t = s->simple_types;
|
|
222
|
+
while (t) {
|
|
223
|
+
XsdSimple* next = t->next;
|
|
224
|
+
free(t->name);
|
|
225
|
+
free(t->base);
|
|
226
|
+
free(t->pattern);
|
|
227
|
+
XsdFacetValue* fv = t->enum_values;
|
|
228
|
+
while (fv) {
|
|
229
|
+
XsdFacetValue* fn = fv->next;
|
|
230
|
+
free(fv->text);
|
|
231
|
+
free(fv);
|
|
232
|
+
fv = fn;
|
|
233
|
+
}
|
|
234
|
+
free(t->min_inclusive);
|
|
235
|
+
free(t->min_exclusive);
|
|
236
|
+
free(t->max_inclusive);
|
|
237
|
+
free(t->max_exclusive);
|
|
238
|
+
free(t->whitespace);
|
|
239
|
+
free(t);
|
|
240
|
+
t = next;
|
|
241
|
+
}
|
|
109
242
|
free(s->error);
|
|
110
243
|
free(s);
|
|
111
244
|
}
|
|
112
245
|
|
|
246
|
+
/* ---- slice 2: lexical validation --------------------------------- */
|
|
247
|
+
|
|
248
|
+
LEPTRIS_API int leptris_xsd_builtin_valid(const char* builtin,
|
|
249
|
+
const char* lexical) {
|
|
250
|
+
extern int xsd_builtin_valid(const char* type, const char* v);
|
|
251
|
+
return xsd_builtin_valid(builtin, lexical);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
LEPTRIS_API int leptris_xsd_simple_valid(LeptrisXsdSchema schema,
|
|
255
|
+
const char* type_name,
|
|
256
|
+
const char* lexical) {
|
|
257
|
+
struct leptris_xsd_schema* s = (struct leptris_xsd_schema*)schema;
|
|
258
|
+
if (!s || !type_name || !lexical) return 0;
|
|
259
|
+
const XsdSimple* t = xsd_find_simple(s, type_name);
|
|
260
|
+
if (!t) return -1; /* unknown type */
|
|
261
|
+
return xsd_valid_chain(s, t, lexical, 0);
|
|
262
|
+
}
|
|
263
|
+
|
|
113
264
|
LEPTRIS_API size_t leptris_xsd_declaration_count(LeptrisXsdSchema schema) {
|
|
114
265
|
if (!schema) return 0;
|
|
115
266
|
return ((struct leptris_xsd_schema*)schema)->declaration_count;
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/* xsd/datatypes.c — #1075 slice 2: built-in datatype lexical
|
|
2
|
+
* validity and user simpleType restriction checking.
|
|
3
|
+
*
|
|
4
|
+
* Built-ins covered here are the tier-1 family: string/Name family,
|
|
5
|
+
* boolean, the numeric family (decimal, integer + its derivations,
|
|
6
|
+
* float, double), the date/time family, duration, hex/base64Binary,
|
|
7
|
+
* anyURI, QName. User simpleTypes validate their base first, then
|
|
8
|
+
* their facets: pattern (the rng_regex engine — the XSD pattern
|
|
9
|
+
* subset is exactly its dialect), enumeration, min/max inclusive/
|
|
10
|
+
* exclusive, the length family, total/fractionDigits. */
|
|
11
|
+
#include <ctype.h>
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
|
|
15
|
+
#include "xsd_internal.h"
|
|
16
|
+
|
|
17
|
+
/* rng_regex.c — the XSD pattern dialect (libxml2-compatible). */
|
|
18
|
+
extern int rng_regex_matches(const char* pat, const char* text);
|
|
19
|
+
|
|
20
|
+
/* ---- character-class helpers ------------------------------------- */
|
|
21
|
+
|
|
22
|
+
static int xsd_ncname_start(char c) {
|
|
23
|
+
return isalpha((unsigned char)c) || c == '_' ||
|
|
24
|
+
(unsigned char)c >= 0x80;
|
|
25
|
+
}
|
|
26
|
+
static int xsd_ncname_char(char c) {
|
|
27
|
+
return xsd_ncname_start(c) || isdigit((unsigned char)c) ||
|
|
28
|
+
c == '-' || c == '.';
|
|
29
|
+
}
|
|
30
|
+
static int xsd_is_ncname(const char* s) {
|
|
31
|
+
if (!s || !*s || !xsd_ncname_start(*s)) return 0;
|
|
32
|
+
for (const char* p = s + 1; *p; p++)
|
|
33
|
+
if (!xsd_ncname_char(*p)) return 0;
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
static int xsd_digits_only(const char* s) {
|
|
37
|
+
if (!s || !*s) return 0;
|
|
38
|
+
for (const char* p = s; *p; p++)
|
|
39
|
+
if (!isdigit((unsigned char)*p)) return 0;
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
/* [-+]?digits — the shared integer lexical shape. */
|
|
43
|
+
static int xsd_signed_integer(const char* s) {
|
|
44
|
+
if (!s || !*s) return 0;
|
|
45
|
+
if (*s == '+' || *s == '-') s++;
|
|
46
|
+
return xsd_digits_only(s);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ---- numeric range table (the integer derivations) --------------- */
|
|
50
|
+
|
|
51
|
+
typedef struct {
|
|
52
|
+
const char* name;
|
|
53
|
+
long long lo, hi;
|
|
54
|
+
int integer; /* 1 = integer lexical; 0 = decimal lexical */
|
|
55
|
+
} NumType;
|
|
56
|
+
|
|
57
|
+
static const NumType k_numerics[] = {
|
|
58
|
+
{"xs:integer", -9223372036854775807LL - 1, 9223372036854775807LL, 1},
|
|
59
|
+
{"xs:nonNegativeInteger", 0, 9223372036854775807LL, 1},
|
|
60
|
+
{"xs:positiveInteger", 1, 9223372036854775807LL, 1},
|
|
61
|
+
{"xs:nonPositiveInteger", -9223372036854775807LL - 1, 0, 1},
|
|
62
|
+
{"xs:negativeInteger", -9223372036854775807LL - 1, -1, 1},
|
|
63
|
+
{"xs:long", -9223372036854775807LL - 1, 9223372036854775807LL, 1},
|
|
64
|
+
{"xs:int", -2147483648LL, 2147483647LL, 1},
|
|
65
|
+
{"xs:short", -32768, 32767, 1},
|
|
66
|
+
{"xs:byte", -128, 127, 1},
|
|
67
|
+
{"xs:unsignedLong", 0, 9223372036854775807LL, 1},
|
|
68
|
+
{"xs:unsignedInt", 0, 4294967295LL, 1},
|
|
69
|
+
{"xs:unsignedShort", 0, 65535, 1},
|
|
70
|
+
{"xs:unsignedByte", 0, 255, 1},
|
|
71
|
+
{NULL, 0, 0, 0},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/* XSD digits: no leading-zero rule beyond the sign for zero, but
|
|
75
|
+
* "007" is a valid integer lexical in XSD (unlike most languages). */
|
|
76
|
+
static int value_in_range(const char* s, long long lo, long long hi) {
|
|
77
|
+
if (!xsd_signed_integer(s)) return 0;
|
|
78
|
+
/* Guard 64-bit overflow: XSD allows arbitrarily many digits —
|
|
79
|
+
* values beyond long long are still "in" xs:integer (arbitrary
|
|
80
|
+
* precision) but cannot sit in a bounded derivation's range.
|
|
81
|
+
* Digit-count prefilter keeps strtoll well-defined. */
|
|
82
|
+
const char* digits = (*s == '+' || *s == '-') ? s + 1 : s;
|
|
83
|
+
size_t nd = strlen(digits);
|
|
84
|
+
while (nd > 1 && digits[0] == '0') { digits++; nd--; }
|
|
85
|
+
if (nd > 19) return 0;
|
|
86
|
+
char* end = NULL;
|
|
87
|
+
long long v = strtoll(s, &end, 10);
|
|
88
|
+
if (end && *end) return 0;
|
|
89
|
+
return v >= lo && v <= hi;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ---- date/time lexical shapes ------------------------------------ */
|
|
93
|
+
|
|
94
|
+
static int four_digits(const char** p) {
|
|
95
|
+
if (!isdigit((unsigned char)(*p)[0]) ||
|
|
96
|
+
!isdigit((unsigned char)(*p)[1]) ||
|
|
97
|
+
!isdigit((unsigned char)(*p)[2]) ||
|
|
98
|
+
!isdigit((unsigned char)(*p)[3]))
|
|
99
|
+
return 0;
|
|
100
|
+
*p += 4;
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
static int two_digits(const char** p) {
|
|
104
|
+
if (!isdigit((unsigned char)(*p)[0]) ||
|
|
105
|
+
!isdigit((unsigned char)(*p)[1]))
|
|
106
|
+
return 0;
|
|
107
|
+
*p += 2;
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
static int tz_optional(const char** p) {
|
|
111
|
+
if (**p != 'Z' && **p != '+' && **p != '-') return 1; /* absent */
|
|
112
|
+
if (**p == 'Z') { (*p)++; return **p == 0; }
|
|
113
|
+
(*p)++;
|
|
114
|
+
return two_digits(p) && *(*p) == ':' && (*p)++ && two_digits(p) &&
|
|
115
|
+
**p == 0;
|
|
116
|
+
}
|
|
117
|
+
static int two_digits_bounded(const char** p, int lo, int hi) {
|
|
118
|
+
if (!two_digits(p)) return 0;
|
|
119
|
+
int v = ((*p)[-2] - '0') * 10 + ((*p)[-1] - '0');
|
|
120
|
+
return v >= lo && v <= hi;
|
|
121
|
+
}
|
|
122
|
+
static int ymd(const char** p, int need_month, int need_day) {
|
|
123
|
+
if (!four_digits(p)) return 0;
|
|
124
|
+
if (need_month) {
|
|
125
|
+
if (*(*p)++ != '-') return 0;
|
|
126
|
+
if (!two_digits_bounded(p, 1, 12)) return 0;
|
|
127
|
+
}
|
|
128
|
+
if (need_day) {
|
|
129
|
+
if (*(*p)++ != '-') return 0;
|
|
130
|
+
if (!two_digits_bounded(p, 1, 31)) return 0;
|
|
131
|
+
}
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
static int time_tail(const char** p) {
|
|
135
|
+
if (!two_digits(p)) return 0;
|
|
136
|
+
if (*(*p)++ != ':') return 0;
|
|
137
|
+
if (!two_digits(p)) return 0;
|
|
138
|
+
if (**p == ':') {
|
|
139
|
+
(*p)++;
|
|
140
|
+
if (!two_digits(p)) return 0;
|
|
141
|
+
if (**p == '.') {
|
|
142
|
+
(*p)++;
|
|
143
|
+
if (!xsd_digits_only(*p)) return 0;
|
|
144
|
+
while (isdigit((unsigned char)**p)) (*p)++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return tz_optional(p);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ---- the built-in table ------------------------------------------ */
|
|
151
|
+
|
|
152
|
+
int xsd_builtin_valid(const char* type, const char* v) {
|
|
153
|
+
if (!type || !v) return 0;
|
|
154
|
+
if (strcmp(type, "xs:string") == 0) return 1;
|
|
155
|
+
if (strcmp(type, "xs:normalizedString") == 0) return 1;
|
|
156
|
+
if (strcmp(type, "xs:token") == 0) {
|
|
157
|
+
if (*v == 0) return 1;
|
|
158
|
+
if (isspace((unsigned char)v[0]) ||
|
|
159
|
+
isspace((unsigned char)v[strlen(v) - 1]))
|
|
160
|
+
return 0;
|
|
161
|
+
for (const char* p = v; *p; p++) {
|
|
162
|
+
if (isspace((unsigned char)*p) &&
|
|
163
|
+
isspace((unsigned char)p[1]))
|
|
164
|
+
return 0;
|
|
165
|
+
if (*p == '\n' || *p == '\r' || *p == '\t') return 0;
|
|
166
|
+
}
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
if (strcmp(type, "xs:Name") == 0) {
|
|
170
|
+
/* Name: NameStartChar (NameChar)* — NCName is the strict
|
|
171
|
+
* ASCII/80 subset; libxml2 accepts ':' in Names. */
|
|
172
|
+
if (!*v || !xsd_ncname_start(*v) && *v != ':') return 0;
|
|
173
|
+
for (const char* p = v + 1; *p; p++)
|
|
174
|
+
if (!xsd_ncname_char(*p) && *p != ':') return 0;
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
if (strcmp(type, "xs:NMTOKEN") == 0) {
|
|
178
|
+
if (!*v) return 0;
|
|
179
|
+
for (const char* p = v; *p; p++)
|
|
180
|
+
if (!xsd_ncname_char(*p) && *p != ':') return 0;
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
if (strcmp(type, "xs:NCName") == 0 || strcmp(type, "xs:ID") == 0 ||
|
|
184
|
+
strcmp(type, "xs:IDREF") == 0 || strcmp(type, "xs:ENTITY") == 0)
|
|
185
|
+
return xsd_is_ncname(v);
|
|
186
|
+
if (strcmp(type, "xs:language") == 0) {
|
|
187
|
+
/* [a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})* */
|
|
188
|
+
const char* p = v;
|
|
189
|
+
int n = 0;
|
|
190
|
+
while (isalpha((unsigned char)*p) && n <= 8) { p++; n++; }
|
|
191
|
+
if (n < 1 || n > 8 || (*p && *p != '-')) return 0;
|
|
192
|
+
while (*p == '-') {
|
|
193
|
+
p++;
|
|
194
|
+
n = 0;
|
|
195
|
+
while ((isalnum((unsigned char)*p)) && n <= 8) { p++; n++; }
|
|
196
|
+
if (n < 1 || n > 8) return 0;
|
|
197
|
+
}
|
|
198
|
+
return *p == 0;
|
|
199
|
+
}
|
|
200
|
+
if (strcmp(type, "xs:anyURI") == 0) {
|
|
201
|
+
/* Permissive (libxml2-compatible): no ASCII control
|
|
202
|
+
* characters, no leading/trailing space. */
|
|
203
|
+
if (*v && (unsigned char)v[0] <= 0x20) return 0;
|
|
204
|
+
size_t n = strlen(v);
|
|
205
|
+
if (n && (unsigned char)v[n - 1] <= 0x20) return 0;
|
|
206
|
+
for (const char* p = v; *p; p++)
|
|
207
|
+
if ((unsigned char)*p < 0x20) return 0;
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
if (strcmp(type, "xs:QName") == 0) {
|
|
211
|
+
const char* colon = strchr(v, ':');
|
|
212
|
+
if (!colon) return xsd_is_ncname(v);
|
|
213
|
+
size_t plen = (size_t)(colon - v);
|
|
214
|
+
char pfx[256];
|
|
215
|
+
if (plen >= sizeof(pfx)) return 0;
|
|
216
|
+
memcpy(pfx, v, plen);
|
|
217
|
+
pfx[plen] = 0;
|
|
218
|
+
return xsd_is_ncname(pfx) && xsd_is_ncname(colon + 1);
|
|
219
|
+
}
|
|
220
|
+
if (strcmp(type, "xs:boolean") == 0)
|
|
221
|
+
return strcmp(v, "true") == 0 || strcmp(v, "false") == 0 ||
|
|
222
|
+
strcmp(v, "0") == 0 || strcmp(v, "1") == 0;
|
|
223
|
+
if (strcmp(type, "xs:decimal") == 0) {
|
|
224
|
+
/* [-+]?((d+(.d*)?)|(.d+)) */
|
|
225
|
+
const char* p = v;
|
|
226
|
+
if (*p == '+' || *p == '-') p++;
|
|
227
|
+
int lead = 0;
|
|
228
|
+
while (isdigit((unsigned char)*p)) { p++; lead = 1; }
|
|
229
|
+
int frac = 0;
|
|
230
|
+
if (*p == '.') {
|
|
231
|
+
p++;
|
|
232
|
+
while (isdigit((unsigned char)*p)) { p++; frac = 1; }
|
|
233
|
+
}
|
|
234
|
+
if (*p) return 0;
|
|
235
|
+
return lead || frac;
|
|
236
|
+
}
|
|
237
|
+
for (const NumType* t = k_numerics; t->name; t++) {
|
|
238
|
+
if (strcmp(type, t->name) == 0) return value_in_range(v, t->lo, t->hi);
|
|
239
|
+
}
|
|
240
|
+
if (strcmp(type, "xs:float") == 0 || strcmp(type, "xs:double") == 0) {
|
|
241
|
+
if (strcmp(v, "INF") == 0 || strcmp(v, "-INF") == 0 ||
|
|
242
|
+
strcmp(v, "NaN") == 0)
|
|
243
|
+
return 1;
|
|
244
|
+
/* decimal or scientific */
|
|
245
|
+
const char* p = v;
|
|
246
|
+
if (*p == '+' || *p == '-') p++;
|
|
247
|
+
int mant = 0;
|
|
248
|
+
while (isdigit((unsigned char)*p)) { p++; mant = 1; }
|
|
249
|
+
if (*p == '.') {
|
|
250
|
+
p++;
|
|
251
|
+
while (isdigit((unsigned char)*p)) { p++; mant = 1; }
|
|
252
|
+
}
|
|
253
|
+
if (!mant) return 0;
|
|
254
|
+
if (*p == 'e' || *p == 'E') {
|
|
255
|
+
p++;
|
|
256
|
+
if (*p == '+' || *p == '-') p++;
|
|
257
|
+
if (!xsd_digits_only(p)) return 0;
|
|
258
|
+
} else if (*p) {
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
if (strcmp(type, "xs:date") == 0) {
|
|
264
|
+
const char* p = v;
|
|
265
|
+
if (*p == '-') p++; /* BC years */
|
|
266
|
+
if (!ymd(&p, 1, 1)) return 0;
|
|
267
|
+
return tz_optional(&p);
|
|
268
|
+
}
|
|
269
|
+
if (strcmp(type, "xs:gYearMonth") == 0) {
|
|
270
|
+
const char* p = v;
|
|
271
|
+
if (*p == '-') p++;
|
|
272
|
+
if (!ymd(&p, 1, 0)) return 0;
|
|
273
|
+
return tz_optional(&p);
|
|
274
|
+
}
|
|
275
|
+
if (strcmp(type, "xs:gYear") == 0) {
|
|
276
|
+
const char* p = v;
|
|
277
|
+
if (*p == '-') p++;
|
|
278
|
+
if (!four_digits(&p)) return 0;
|
|
279
|
+
return tz_optional(&p);
|
|
280
|
+
}
|
|
281
|
+
if (strcmp(type, "xs:gMonthDay") == 0) {
|
|
282
|
+
const char* p = v;
|
|
283
|
+
if (p[0] != '-' || p[1] != '-') return 0;
|
|
284
|
+
p += 2;
|
|
285
|
+
if (!two_digits(&p)) return 0;
|
|
286
|
+
if (*p == 0) return 1;
|
|
287
|
+
if (*p++ != '-') return 0;
|
|
288
|
+
return two_digits(&p) && *p == 0;
|
|
289
|
+
}
|
|
290
|
+
if (strcmp(type, "xs:gDay") == 0) {
|
|
291
|
+
const char* p = v;
|
|
292
|
+
if (*p++ != '-') return 0;
|
|
293
|
+
if (*p++ != '-') return 0;
|
|
294
|
+
return two_digits(&p) && *p == 0;
|
|
295
|
+
}
|
|
296
|
+
if (strcmp(type, "xs:gMonth") == 0) {
|
|
297
|
+
const char* p = v;
|
|
298
|
+
if (*p++ != '-') return 0;
|
|
299
|
+
if (*p++ != '-') return 0;
|
|
300
|
+
return two_digits(&p) && *p == 0;
|
|
301
|
+
}
|
|
302
|
+
if (strcmp(type, "xs:dateTime") == 0) {
|
|
303
|
+
const char* p = v;
|
|
304
|
+
if (*p == '-') p++;
|
|
305
|
+
if (!ymd(&p, 1, 1)) return 0;
|
|
306
|
+
if (*p != 'T' && *p != ' ') return 0;
|
|
307
|
+
p++;
|
|
308
|
+
return time_tail(&p);
|
|
309
|
+
}
|
|
310
|
+
if (strcmp(type, "xs:time") == 0) {
|
|
311
|
+
const char* p = v;
|
|
312
|
+
return time_tail(&p);
|
|
313
|
+
}
|
|
314
|
+
if (strcmp(type, "xs:duration") == 0 ||
|
|
315
|
+
strcmp(type, "xs:dayTimeDuration") == 0 ||
|
|
316
|
+
strcmp(type, "xs:yearMonthDuration") == 0) {
|
|
317
|
+
/* PnYnMnDTnHnMnS, at least one component; optional sign. */
|
|
318
|
+
const char* p = v;
|
|
319
|
+
if (*p == '-' || *p == '+') p++;
|
|
320
|
+
if (*p++ != 'P') return 0;
|
|
321
|
+
int any = 0;
|
|
322
|
+
int in_time = 0;
|
|
323
|
+
for (;;) {
|
|
324
|
+
if (*p == 'T') {
|
|
325
|
+
if (in_time) return 0;
|
|
326
|
+
in_time = 1;
|
|
327
|
+
p++;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (!isdigit((unsigned char)*p)) break;
|
|
331
|
+
while (isdigit((unsigned char)*p)) p++;
|
|
332
|
+
if (*p == '.') { p++; while (isdigit((unsigned char)*p)) p++; }
|
|
333
|
+
char unit = *p;
|
|
334
|
+
if (!unit) return 0;
|
|
335
|
+
/* Months (date) and minutes (time) share 'M' — the
|
|
336
|
+
* in_time flag disambiguates, so only the unambiguous
|
|
337
|
+
* units are side-checked. */
|
|
338
|
+
if (in_time && unit == 'Y') return 0;
|
|
339
|
+
if (!in_time && (unit == 'H' || unit == 'S')) return 0;
|
|
340
|
+
p++;
|
|
341
|
+
any = 1;
|
|
342
|
+
}
|
|
343
|
+
return any && *p == 0;
|
|
344
|
+
}
|
|
345
|
+
if (strcmp(type, "xs:hexBinary") == 0) {
|
|
346
|
+
size_t n = strlen(v);
|
|
347
|
+
if (n % 2) return 0;
|
|
348
|
+
for (const char* p = v; *p; p++)
|
|
349
|
+
if (!isxdigit((unsigned char)*p)) return 0;
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
if (strcmp(type, "xs:base64Binary") == 0) {
|
|
353
|
+
size_t n = strlen(v);
|
|
354
|
+
if (!n) return 1;
|
|
355
|
+
size_t i = 0;
|
|
356
|
+
while (i + 4 <= n) {
|
|
357
|
+
for (int k = 0; k < 4; k++) {
|
|
358
|
+
char c = v[i + k];
|
|
359
|
+
if (!isalnum((unsigned char)c) && c != '+' && c != '/' &&
|
|
360
|
+
!(k == 3 && c == '='))
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
i += 4;
|
|
364
|
+
}
|
|
365
|
+
for (; i < n; i++) {
|
|
366
|
+
char c = v[i];
|
|
367
|
+
if (!isalnum((unsigned char)c) && c != '+' && c != '/' &&
|
|
368
|
+
c != '=')
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
return 1;
|
|
372
|
+
}
|
|
373
|
+
return -1; /* not in the tier-1 table */
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ---- facet checking on a captured simpleType --------------------- */
|
|
377
|
+
|
|
378
|
+
int xsd_simple_valid(const XsdSimple* simple, const char* v);
|
|
379
|
+
|
|
380
|
+
/* The base chain resolves to a built-in reference: validate the
|
|
381
|
+
* lexical against it first. */
|
|
382
|
+
static int base_valid(const char* base, const char* v) {
|
|
383
|
+
if (!base) return xsd_builtin_valid("xs:string", v);
|
|
384
|
+
if (strncmp(base, "xs:", 3) == 0) return xsd_builtin_valid(base, v);
|
|
385
|
+
return -1; /* local base — the resolver walks the chain */
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static int facet_check(const XsdSimple* s, const char* v) {
|
|
389
|
+
if (s->pattern && !rng_regex_matches(s->pattern, v)) return 0;
|
|
390
|
+
if (s->enum_values) {
|
|
391
|
+
int found = 0;
|
|
392
|
+
for (XsdFacetValue* e = s->enum_values; e; e = e->next)
|
|
393
|
+
if (strcmp(e->text, v) == 0) { found = 1; break; }
|
|
394
|
+
if (!found) return 0;
|
|
395
|
+
}
|
|
396
|
+
/* Length family: character count (string-family semantics). */
|
|
397
|
+
size_t n = strlen(v);
|
|
398
|
+
if (s->length >= 0 && (long)n != s->length) return 0;
|
|
399
|
+
if (s->min_length >= 0 && (long)n < s->min_length) return 0;
|
|
400
|
+
if (s->max_length >= 0 && (long)n > s->max_length) return 0;
|
|
401
|
+
|
|
402
|
+
/* Numeric min/max when the value parses as a double. */
|
|
403
|
+
char* end = NULL;
|
|
404
|
+
double d = strtod(v, &end);
|
|
405
|
+
if (end && *end == 0 && end != v) {
|
|
406
|
+
if (s->min_inclusive && !(d >= strtod(s->min_inclusive, NULL)))
|
|
407
|
+
return 0;
|
|
408
|
+
if (s->min_exclusive && !(d > strtod(s->min_exclusive, NULL)))
|
|
409
|
+
return 0;
|
|
410
|
+
if (s->max_inclusive && !(d <= strtod(s->max_inclusive, NULL)))
|
|
411
|
+
return 0;
|
|
412
|
+
if (s->max_exclusive && !(d < strtod(s->max_exclusive, NULL)))
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
if (s->total_digits >= 0) {
|
|
416
|
+
long total = 0, frac = 0, seen_frac = 0;
|
|
417
|
+
for (const char* p = v; *p; p++) {
|
|
418
|
+
if (isdigit((unsigned char)*p)) {
|
|
419
|
+
total++;
|
|
420
|
+
if (seen_frac) frac++;
|
|
421
|
+
} else if (*p == '.') {
|
|
422
|
+
seen_frac = 1;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (total > s->total_digits) return 0;
|
|
426
|
+
if (s->fraction_digits >= 0 && frac > s->fraction_digits)
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
int xsd_simple_valid(const XsdSimple* simple, const char* v) {
|
|
433
|
+
if (!simple || !v) return 0;
|
|
434
|
+
int base = base_valid(simple->base, v);
|
|
435
|
+
if (base == 0) return 0;
|
|
436
|
+
/* base == -1 (local base) delegates to the resolver's chain in
|
|
437
|
+
* the caller; facets still apply here. */
|
|
438
|
+
return facet_check(simple, v);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Pattern/enumeration defaults for whitespace — slice 3 grows the
|
|
442
|
+
* whitespace facet (collapse/replace) into the value pipeline. */
|
|
443
|
+
const char* xsd_simple_whitespace(const XsdSimple* simple) {
|
|
444
|
+
return simple ? simple->whitespace : NULL;
|
|
445
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* xsd/xsd_internal.h — XSD subsystem internals (#1075 tier 1).
|
|
2
|
+
* Copyright (c) 2024, Ribose Inc. */
|
|
3
|
+
#ifndef LEPTRIS_XSD_INTERNAL_H
|
|
4
|
+
#define LEPTRIS_XSD_INTERNAL_H
|
|
5
|
+
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
|
|
8
|
+
#ifdef __cplusplus
|
|
9
|
+
extern "C" {
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
/* One xs:enumeration / facet value, schema-owned. */
|
|
13
|
+
typedef struct xsd_facet_value {
|
|
14
|
+
char* text;
|
|
15
|
+
struct xsd_facet_value* next;
|
|
16
|
+
} XsdFacetValue;
|
|
17
|
+
|
|
18
|
+
/* A user simpleType captured at compile time: name, restriction
|
|
19
|
+
* base (a built-in reference like "xs:integer" or another local
|
|
20
|
+
* simpleType name), and the facets the restriction carries. */
|
|
21
|
+
typedef struct xsd_simple {
|
|
22
|
+
char* name;
|
|
23
|
+
char* base;
|
|
24
|
+
char* pattern;
|
|
25
|
+
XsdFacetValue* enum_values;
|
|
26
|
+
size_t enum_count;
|
|
27
|
+
char* min_inclusive;
|
|
28
|
+
char* min_exclusive;
|
|
29
|
+
char* max_inclusive;
|
|
30
|
+
char* max_exclusive;
|
|
31
|
+
long length;
|
|
32
|
+
long min_length;
|
|
33
|
+
long max_length; /* -1 = absent */
|
|
34
|
+
long total_digits;
|
|
35
|
+
long fraction_digits;
|
|
36
|
+
char* whitespace; /* "preserve" | "replace" | "collapse" */
|
|
37
|
+
struct xsd_simple* next;
|
|
38
|
+
} XsdSimple;
|
|
39
|
+
|
|
40
|
+
#ifdef __cplusplus
|
|
41
|
+
}
|
|
42
|
+
#endif
|
|
43
|
+
#endif /* LEPTRIS_XSD_INTERNAL_H */
|
|
@@ -1363,3 +1363,18 @@ TEST(RngParse, FileErrorsPublishToLastError) {
|
|
|
1363
1363
|
ASSERT_NE(err, nullptr);
|
|
1364
1364
|
EXPECT_NE(strstr(err, "open"), nullptr) << err;
|
|
1365
1365
|
}
|
|
1366
|
+
|
|
1367
|
+
// #1075 slice 2 found it: the {n} EXACT quantifier parsed as
|
|
1368
|
+
// open-ended {n,} (the no-comma branch never set the hi bound), so
|
|
1369
|
+
// [A-Z]{3} matched ABCD. Any RNG schema with exact bounds silently
|
|
1370
|
+
// accepted longer runs. Never-again spec on the direct engine call.
|
|
1371
|
+
TEST(RngRegex, ExactQuantifierIsClosed) {
|
|
1372
|
+
EXPECT_EQ(rng_regex_matches("[A-Z]{3}", "ABC"), 1);
|
|
1373
|
+
EXPECT_EQ(rng_regex_matches("[A-Z]{3}", "ABCD"), 0);
|
|
1374
|
+
EXPECT_EQ(rng_regex_matches("[A-Z]{3}", "AB"), 0);
|
|
1375
|
+
EXPECT_EQ(rng_regex_matches("a{2,3}", "aa"), 1);
|
|
1376
|
+
EXPECT_EQ(rng_regex_matches("a{2,3}", "aaa"), 1);
|
|
1377
|
+
EXPECT_EQ(rng_regex_matches("a{2,3}", "aaaa"), 0);
|
|
1378
|
+
EXPECT_EQ(rng_regex_matches("a{2}", "aa"), 1);
|
|
1379
|
+
EXPECT_EQ(rng_regex_matches("a{2}", "aaa"), 0);
|
|
1380
|
+
}
|
|
@@ -152,3 +152,115 @@ TEST(Libxml2Corpus, EveryFixtureCompilesOrFailsCleanly) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
} // namespace
|
|
155
|
+
|
|
156
|
+
// ---- slice 2: lexical validation ----------------------------------
|
|
157
|
+
namespace {
|
|
158
|
+
|
|
159
|
+
struct BuiltinCase {
|
|
160
|
+
const char* type;
|
|
161
|
+
const char* value;
|
|
162
|
+
int expect; /* 1 valid, 0 invalid, -1 not-in-table */
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
TEST(XsdDatatypes, BuiltinsLexicalMatrix) {
|
|
166
|
+
const BuiltinCase cases[] = {
|
|
167
|
+
{"xs:integer", "42", 1}, {"xs:integer", "-7", 1},
|
|
168
|
+
{"xs:integer", "+007", 1}, {"xs:integer", "3.5", 0},
|
|
169
|
+
{"xs:integer", "", 0}, {"xs:integer", "12a", 0},
|
|
170
|
+
{"xs:byte", "127", 1}, {"xs:byte", "128", 0},
|
|
171
|
+
{"xs:unsignedInt", "0", 1}, {"xs:unsignedInt", "-1", 0},
|
|
172
|
+
{"xs:positiveInteger", "1", 1}, {"xs:positiveInteger", "0", 0},
|
|
173
|
+
{"xs:decimal", "-0.5", 1}, {"xs:decimal", ".5", 1},
|
|
174
|
+
{"xs:decimal", "5.", 1}, {"xs:decimal", ".", 0},
|
|
175
|
+
{"xs:boolean", "true", 1}, {"xs:boolean", "2", 0},
|
|
176
|
+
{"xs:double", "1.5e10", 1}, {"xs:double", "INF", 1},
|
|
177
|
+
{"xs:date", "2026-10-08", 1}, {"xs:date", "2026-13-01", 0},
|
|
178
|
+
{"xs:dateTime", "2026-10-08T12:30:00", 1},
|
|
179
|
+
{"xs:time", "23:59:59.123", 1}, {"xs:time", "24:00:00", 1},
|
|
180
|
+
{"xs:duration", "P1Y2M3DT4H5M6S", 1},
|
|
181
|
+
{"xs:duration", "P", 0},
|
|
182
|
+
{"xs:hexBinary", "0A1F", 1}, {"xs:hexBinary", "0A1", 0},
|
|
183
|
+
{"xs:base64Binary", "QUJD", 1}, {"xs:base64Binary", "!", 0},
|
|
184
|
+
{"xs:NCName", "_a1", 1}, {"xs:NCName", "1a", 0},
|
|
185
|
+
{"xs:token", "a b", 1}, {"xs:token", "a b", 0},
|
|
186
|
+
{"xs:language", "en", 1}, {"xs:language", "e!", 0},
|
|
187
|
+
{"xs:anyURI", "http://x/y", 1},
|
|
188
|
+
{"xs:notAThing", "x", -1},
|
|
189
|
+
};
|
|
190
|
+
for (const BuiltinCase& c : cases) {
|
|
191
|
+
SCOPED_TRACE(std::string(c.type) + " : " + c.value);
|
|
192
|
+
EXPECT_EQ(leptris_xsd_builtin_valid(c.type, c.value), c.expect)
|
|
193
|
+
<< c.type << " : " << c.value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
TEST(XsdDatatypes, RestrictionFacetsApply) {
|
|
198
|
+
const char* xsd =
|
|
199
|
+
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
|
|
200
|
+
"<xs:simpleType name=\"count\">"
|
|
201
|
+
"<xs:restriction base=\"xs:integer\">"
|
|
202
|
+
"<xs:minInclusive value=\"1\"/>"
|
|
203
|
+
"<xs:maxInclusive value=\"10\"/>"
|
|
204
|
+
"</xs:restriction></xs:simpleType>"
|
|
205
|
+
"<xs:simpleType name=\"code\">"
|
|
206
|
+
"<xs:restriction base=\"xs:string\">"
|
|
207
|
+
"<xs:pattern value=\"[A-Z]{3}\"/>"
|
|
208
|
+
"</xs:restriction></xs:simpleType>"
|
|
209
|
+
"<xs:simpleType name=\"color\">"
|
|
210
|
+
"<xs:restriction base=\"xs:string\">"
|
|
211
|
+
"<xs:enumeration value=\"red\"/>"
|
|
212
|
+
"<xs:enumeration value=\"green\"/>"
|
|
213
|
+
"</xs:restriction></xs:simpleType>"
|
|
214
|
+
"<xs:simpleType name=\"label\">"
|
|
215
|
+
"<xs:restriction base=\"xs:string\">"
|
|
216
|
+
"<xs:minLength value=\"2\"/>"
|
|
217
|
+
"<xs:maxLength value=\"4\"/>"
|
|
218
|
+
"</xs:restriction></xs:simpleType>"
|
|
219
|
+
"</xs:schema>";
|
|
220
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
221
|
+
LeptrisXsdSchema s = leptris_xsd_compile(xsd, strlen(xsd), &st);
|
|
222
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
223
|
+
EXPECT_EQ(st, LEPTRIS_OK);
|
|
224
|
+
|
|
225
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "count", "5"), 1);
|
|
226
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "count", "0"), 0);
|
|
227
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "count", "11"), 0);
|
|
228
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "count", "x"), 0);
|
|
229
|
+
|
|
230
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "code", "ABC"), 1);
|
|
231
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "code", "ABCD"), 0);
|
|
232
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "code", "abc"), 0);
|
|
233
|
+
|
|
234
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "color", "red"), 1);
|
|
235
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "color", "blue"), 0);
|
|
236
|
+
|
|
237
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "label", "ab"), 1);
|
|
238
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "label", "abcde"), 0);
|
|
239
|
+
|
|
240
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "missing", "x"), -1);
|
|
241
|
+
leptris_xsd_free(s);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
TEST(XsdDatatypes, LocalBaseChainValidatesEveryHop) {
|
|
245
|
+
const char* xsd =
|
|
246
|
+
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
|
|
247
|
+
"<xs:simpleType name=\"outer\">"
|
|
248
|
+
"<xs:restriction base=\"inner\">"
|
|
249
|
+
"<xs:maxInclusive value=\"100\"/>"
|
|
250
|
+
"</xs:restriction></xs:simpleType>"
|
|
251
|
+
"<xs:simpleType name=\"inner\">"
|
|
252
|
+
"<xs:restriction base=\"xs:integer\">"
|
|
253
|
+
"<xs:minInclusive value=\"10\"/>"
|
|
254
|
+
"</xs:restriction></xs:simpleType>"
|
|
255
|
+
"</xs:schema>";
|
|
256
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
257
|
+
LeptrisXsdSchema s = leptris_xsd_compile(xsd, strlen(xsd), &st);
|
|
258
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
259
|
+
/* Both hops' facets bind: 5 fails inner, 150 fails outer. */
|
|
260
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "outer", "50"), 1);
|
|
261
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "outer", "5"), 0);
|
|
262
|
+
EXPECT_EQ(leptris_xsd_simple_valid(s, "outer", "150"), 0);
|
|
263
|
+
leptris_xsd_free(s);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
} // namespace
|
|
@@ -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.319",
|
|
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,7 +1,7 @@
|
|
|
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.319.0
|
|
5
5
|
platform: arm-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -697,6 +697,8 @@ files:
|
|
|
697
697
|
- vendor-src/libleptris/src/leptris/xpath/xpath_variables.h
|
|
698
698
|
- vendor-src/libleptris/src/leptris/xquery/xquery.c
|
|
699
699
|
- vendor-src/libleptris/src/leptris/xsd/compile.c
|
|
700
|
+
- vendor-src/libleptris/src/leptris/xsd/datatypes.c
|
|
701
|
+
- vendor-src/libleptris/src/leptris/xsd/xsd_internal.h
|
|
700
702
|
- vendor-src/libleptris/src/leptris/xslt/xslt_exec.c
|
|
701
703
|
- vendor-src/libleptris/src/leptris/xslt/xslt_functions.c
|
|
702
704
|
- vendor-src/libleptris/src/leptris/xslt/xslt_internal.h
|