leptris 1.9.318.0 → 1.9.320.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
  5. data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
  6. data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
  7. data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
  8. data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
  9. data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
  10. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  11. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  12. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  13. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  14. data/lib/leptris/version.rb +1 -1
  15. data/lib/leptris/xml/ffi.rb +9 -0
  16. data/lib/leptris/xml/xsd.rb +32 -0
  17. data/vendor-src/README.md +1 -1
  18. data/vendor-src/libleptris/CHANGELOG.md +57 -0
  19. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  20. data/vendor-src/libleptris/src/CMakeLists.txt +1 -0
  21. data/vendor-src/libleptris/src/include/leptris.h +11 -0
  22. data/vendor-src/libleptris/src/leptris/descriptor/plan.c +35 -3
  23. data/vendor-src/libleptris/src/leptris/rng/rng_internal.h +8 -0
  24. data/vendor-src/libleptris/src/leptris/rng/rng_regex.c +4 -0
  25. data/vendor-src/libleptris/src/leptris/xsd/compile.c +153 -2
  26. data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +445 -0
  27. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +43 -0
  28. data/vendor-src/libleptris/test/abi/test_descriptor.cpp +168 -0
  29. data/vendor-src/libleptris/test/rng/test_rng.cpp +15 -0
  30. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +112 -0
  31. data/vendor-src/libleptris/vcpkg.json +1 -1
  32. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6c90b838dcbd3165155494321e171c57f470145b959ee40656f40dce4f84897
4
- data.tar.gz: 6c7e1f5205388ca6a0f476378e04ca235f48134e02b25873392d59b013a77698
3
+ metadata.gz: 7e309fd6444a641db0b0f24f92aef96097860678c006a217a783724c499b7f50
4
+ data.tar.gz: d041122c9d4d1b366549b4dd2d81c8e6c8ee226cc8ddee62d30a6f9a7a582458
5
5
  SHA512:
6
- metadata.gz: ba48e81789e2dfc95c4c4da2c1c11e7609f2b0f32c9449d65681150ceeb169696bc07a2af29cf12be6514231973a41f580307ec50300dd6b7669ba8489a282ba
7
- data.tar.gz: 670b0d55d9fc1a1eb3e4ce595e8505fab58be7803618a39229adfba8ba5a4d7204bdb4b807888022b92ed3f00d93dd6374b73997b01368a1bd584212cc3bc027
6
+ metadata.gz: ae5b672a25949b41161419054c7ba635e2d234a5ea635a195ad5002d2aeaff4a4c18c3b0e98af5c23cb09c1fc7cec6967e4e866ea4b96c44031e0422fe1545fd
7
+ data.tar.gz: 6f454b0ad8bc92f31196fca9eb28369118cfe9ed81a7ccff3563a9dd903345ff145cdcb9549bcb6efc7d087718f143e1338704f4d6fdd63face059b74369f97f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ ## [1.9.320.0] - 2026-10-08
2
+
3
+ ### Fixed — engine sync (libleptris 1.9.320)
4
+
5
+ - **Exact-URI child rows survive build-anchor GC (#1585)**: the
6
+ engine's plan build let child-row `ns_uri` ride a pointer into
7
+ the caller's build buffers instead of deep-copying — bindings
8
+ GC their anchors after build, so exact-URI child rows
9
+ deterministically stopped matching (uniword's
10
+ dc:title/dc:creator/dc:description hydrated nil). The engine
11
+ retains the URI; pinned with build + GC + walk.
12
+ - **Plain attribute rows match any qualification (#1586)**:
13
+ ns-unset attr rows try the exact wire spelling first, then any
14
+ qualification by local name (#758 parity) — plain rows bind
15
+ `w:name`/`w:val` exactly as the interpretive path does, top
16
+ level and nested; exact-URI rows keep their #1486 precedence.
17
+ Pinned at both depths.
18
+
19
+ ## [1.9.319.0] - 2026-10-08
20
+
21
+ ### Added — engine sync (libleptris 1.9.319)
22
+
23
+ - **XSD lexical validation, slice 2 (#1075)**:
24
+ `XSD.builtin_valid?` covers the tier-1 built-in table (string
25
+ family, token, boolean, decimal/integer ranges, float/double,
26
+ date/time with bounds, duration, hex/base64Binary) by the
27
+ "xs:NAME" reference spelling; `Schema#simple_valid?` resolves
28
+ user simpleTypes through cycle-guarded restriction chains
29
+ (pattern / enumeration / min-max / length / digits facets).
30
+ The C face's -1 (not-in-table / unknown type) raises — a silent
31
+ false would let misspelled types validate nothing forever.
32
+ (Slice 1's compile surface rode in the parallel 1.9.318.0.)
33
+
34
+ ### Fixed — engine sync (1.9.319)
35
+
36
+ - **RELAX NG exact `{n}` quantifiers (PR #1583)**: the regex
37
+ engine's no-comma branch never set the hi bound — `[A-Z]{3}`
38
+ matched `ABCD`, so any schema using exact bounds silently
39
+ over-accepted. Pinned through the binding's datatype path
40
+ (accepts ABC, rejects ABCD).
41
+
42
+ ### Performance — engine sync (1.9.318)
43
+
44
+ - Per-thread small-span arena recycle (#1436): the tiny-doc
45
+ create/destroy row drops ~15% (183→159 ns/doc quiet-hardware) —
46
+ engine-internal; the binding inherits it on every
47
+ Document.create cycle.
48
+
1
49
  ## [1.9.317.1] - 2026-10-08
2
50
 
3
51
  ### 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.320"
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.320.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/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.320
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,62 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.320] - 2026-10-08
4
+
5
+ ### Fixed
6
+
7
+ - **Descriptor: child-row exact-URI `ns_uri` retained at build
8
+ (#1585, PR #1587).** The build deep-copied child `ns_prefix` but
9
+ let `ns_uri` ride the struct copy — a pointer into the caller's
10
+ build buffers. Bindings GC their anchors (a warm walk plus one GC
11
+ makes it deterministic), after which exact-URI child rows
12
+ silently stopped matching and hydration answered nil (uniword's
13
+ `dc:title`/`dc:creator`/`dc:description`). The spec trashes the
14
+ caller's buffer after build and asserts the row still binds.
15
+ - **Descriptor: plain attribute rows match any qualification
16
+ (#1586, PR #1587).** `dp_attr_value`'s ns-unset path now mirrors
17
+ #758 — the exact wire spelling first, then any qualification by
18
+ local name — so plain rows bind `w:name`/`w:val` exactly as the
19
+ interpretive path does, top level and nested. Exact-URI rows keep
20
+ their #1486 precedence. A NESTED row's binding also consults the
21
+ TARGET plan's `ns_lenient` now (the #1115-consistent authority)
22
+ instead of the outer plan's.
23
+
24
+
25
+
26
+ ## [1.9.319] - 2026-10-08
27
+
28
+ ### Added
29
+
30
+ - **XSD validation, tier 1, slices 1–2 (#1075, PRs #1581/#1583).**
31
+ Slice 1: the `leptris_xsd_compile` surface (xs:schema identity
32
+ under any namespace spelling, top-level declaration enumeration,
33
+ error-carrying failure handles) plus the corpus gate — libxml2's
34
+ own `test/schemas` set (350 files, MIT) vendored at
35
+ `test/xsd/libxml2-cases`, **163/163 fixtures compiling** with a
36
+ pinned monotonic floor. Slice 2: `leptris_xsd_builtin_valid` —
37
+ the tier-1 built-in lexical table (string/Name family, token,
38
+ language, anyURI, QName, boolean, decimal, the integer-derivation
39
+ ranges, float/double, the date/time family with month/day bounds,
40
+ duration, hex/base64Binary) — and `leptris_xsd_simple_valid`:
41
+ named simpleTypes captured at compile (base + pattern /
42
+ enumeration / min-max inclusive-exclusive / length family /
43
+ total-fractionDigits facets), validated through cycle-guarded
44
+ restriction chains where every hop's facets bind.
45
+
46
+ ### Fixed
47
+
48
+ - **Regex engine: `{n}` EXACT quantifiers parsed open-ended
49
+ (PR #1583).** The no-comma branch never set the hi bound, so
50
+ `[A-Z]{3}` matched `ABCD` — any RELAX NG schema using exact
51
+ quantifier bounds silently accepted longer runs. Root fix in the
52
+ quantifier parser plus a never-again spec on the direct engine
53
+ call; `rng_internal.h` gained C++ guards for test-side calls.
54
+ - Amalgamated single-TU build: the XSD datatype statics now carry
55
+ the `xsd_` prefix (collision with the xpath lexer's same-named
56
+ statics — the #1217 class).
57
+
58
+
59
+
3
60
  ## [1.9.318] - 2026-10-08
4
61
 
5
62
  ### 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.320
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,
@@ -285,6 +285,7 @@ static void dp_plan_free(LeptrisPlan p) {
285
285
  for (uint32_t c = 0; c < d->child_count; c++) {
286
286
  free((char *)d->child_plans[c].wire_name);
287
287
  free((char *)d->child_plans[c].ns_prefix);
288
+ free((char *)d->child_plans[c].ns_uri);
288
289
  for (uint16_t pi = 0;
289
290
  pi < d->child_plans[c].predicate_count; pi++) {
290
291
  free((char*)d->child_plans[c]
@@ -481,6 +482,15 @@ LEPTRIS_API LeptrisPlan leptris_plan_build(const leptris_plan_spec* spec,
481
482
  sc->ns_prefix ? dp_strdup(sc->ns_prefix) : NULL;
482
483
  if (sc->ns_prefix && !d->child_plans[c].ns_prefix)
483
484
  goto oom;
485
+ /* #1585: ns_uri rides the struct copy otherwise —
486
+ * a dangling pointer into the caller's build
487
+ * buffers once they are reclaimed (bindings GC
488
+ * their anchors; the descriptor must outlive
489
+ * them). Same retention as attr rows (#1486). */
490
+ d->child_plans[c].ns_uri =
491
+ sc->ns_uri ? dp_strdup(sc->ns_uri) : NULL;
492
+ if (sc->ns_uri && !d->child_plans[c].ns_uri)
493
+ goto oom;
484
494
  uint16_t pc = sc->predicate_count;
485
495
  if (pc) {
486
496
  if (!sc->predicates) goto oom;
@@ -598,8 +608,25 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
598
608
  * namespace (XML namespaces §5.2) — EXACT never binds them. */
599
609
  static const char* dp_attr_value(LeptrisElement elem,
600
610
  const leptris_attr_plan* ap) {
601
- if (!ap->ns_form)
602
- return leptris_element_attribute(elem, ap->wire_name);
611
+ if (!ap->ns_form) {
612
+ /* #1586, the plan-path mirror of #758: the exact wire
613
+ * spelling first, then any qualification by local name —
614
+ * plain rows are lenient exactly like element rows under
615
+ * ns_lenient. Exact-URI rows keep their #1486 semantics. */
616
+ const char* exact =
617
+ leptris_element_attribute(elem, ap->wire_name);
618
+ if (exact) return exact;
619
+ for (LeptrisAttribute at = leptris_element_first_attribute(elem);
620
+ at; at = leptris_attribute_next(at)) {
621
+ const char* qn = leptris_attribute_get_name(at);
622
+ if (!qn) continue;
623
+ const char* colon = strchr(qn, ':');
624
+ const char* local = colon ? colon + 1 : qn;
625
+ if (strcmp(local, ap->wire_name) == 0)
626
+ return leptris_attribute_get_value(elem, at);
627
+ }
628
+ return NULL;
629
+ }
603
630
  for (LeptrisAttribute at = leptris_element_first_attribute(elem);
604
631
  at; at = leptris_attribute_next(at)) {
605
632
  const char* qn = leptris_attribute_get_name(at);
@@ -832,8 +859,13 @@ static int dp_walk_children(LeptrisElement elem, const dp_plan* plan,
832
859
  LeptrisElement child = (LeptrisElement)n;
833
860
  if (!dp_wire_name_matches(child, row->wire_name))
834
861
  continue;
862
+ /* The TARGET plan's form governs (#1115) — so its
863
+ * lenient flag governs the leniency, not the outer
864
+ * plan's (#1586: a namespaced child under a plan
865
+ * that declares ns_lenient binds by local name). */
835
866
  if (!dp_ns_binds(child, target->ns_form, target->ns_uri,
836
- lenient))
867
+ (target->flags &
868
+ LEPTRIS_PLAN_FLAG_NS_LENIENT) != 0))
837
869
  continue;
838
870
  /* Claim the element against later same-wire rows
839
871
  * (predicate partitioning). */
@@ -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;