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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee7babaf2291f4504600ca04e9ae6bc2ec2ae180b6ab7ebaae467ad175ed240e
4
- data.tar.gz: 1be2ebb114b6f49629edd3c7200ac9cc56f4afe8e346d13731d83fe8f6bb1681
3
+ metadata.gz: 2d73bfbca7f678b33985e74690f98802f0f020a438cb7f248df62b4531c3f33b
4
+ data.tar.gz: 81923f67f5adb8e770fa8d9228677b58f33990babc4261a38766c0186b00654e
5
5
  SHA512:
6
- metadata.gz: a1fd9336ea9f80f28aa9768e387e8e7a1475de21a930ec4204047c9dc8d17f61812c97e74bf2c30b54f7c0b6fbfa7b49715b5e0a2c5be1c6ff860fe1cac6f175
7
- data.tar.gz: baf8c3639dd59c27fa6a2a3bb5e1feb9449b6763a7fc221ca3cb00f5320a0ead3caf691f6c573fddaee99397d1b1679feb226bd28c97ba094a2a4627a93f8af6
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.318"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.318.0"
4
+ VERSION = "1.9.319.0"
5
5
  end
@@ -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
@@ -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,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.318
4
+ - libleptris v1.9.319
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.318
6
+ VERSION 1.9.319
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -28,6 +28,7 @@ set(LEPTRIS_SOURCES
28
28
  leptris/diff/diff.c
29
29
  leptris/rng/parse.c
30
30
  leptris/xsd/compile.c
31
+ leptris/xsd/datatypes.c
31
32
  leptris/rng/rng_public.c
32
33
  leptris/rng/rng_regex.c
33
34
  leptris/rng/validate.c
@@ -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
- s->declaration_count++;
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.318",
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.318.0
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