leptris 1.9.323.0 → 1.9.324.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +21 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +164 -0
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +213 -0
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +20 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +119 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20ca01027af6d2d9c7dd54b359eb38fb8243682d85499c82fa021f22605718f6
|
|
4
|
+
data.tar.gz: 67293f668204144634cac5a732f48376f218ac225378f1139809900ccd459c06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a6421039f5259c8887348842d639ab31c270fb628b22538b51d5426093aee61dce662de3a5c1386a00faa8fbd6cc2ddc715fe856c14ed3dc0e073c369491858
|
|
7
|
+
data.tar.gz: 17a7df64ddb31b03471b14fcbe99c15ced17574da99709fa316de42b8176b62c8cc6b6722f6c8d6f3529981e6b53b4e7b28ec1474046bd33fb2dceebe33ca8f7
|
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.324"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.324] - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **XSD identity constraints — tier-1 scope complete (#1075,
|
|
8
|
+
PR #1598).** `xs:key` / `xs:unique` / `xs:keyref` capture on
|
|
9
|
+
top-level element declarations (direct children or inside the
|
|
10
|
+
inline complexType) and two-pass instance validation driven by
|
|
11
|
+
the engine's own XPath with element context: pass 1 collects
|
|
12
|
+
key/unique field tuples (string-value extraction — attribute
|
|
13
|
+
fields included), enforces uniqueness, and requires non-empty
|
|
14
|
+
key fields; pass 2 collects keyref tuples; resolution then
|
|
15
|
+
requires every keyref to match a collected key/unique tuple.
|
|
16
|
+
The selector reads the `xs:selector` child's `@xpath`. Tuple
|
|
17
|
+
tables deep-copy their strings; IC captures free with the
|
|
18
|
+
schema. This completes the tier-1 engine scope: compile →
|
|
19
|
+
datatypes/facets → content models → instance validation →
|
|
20
|
+
identity constraints, gated on libxml2's 163-fixture corpus.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
3
24
|
## [1.9.323] - 2026-10-08
|
|
4
25
|
|
|
5
26
|
### Fixed
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
#define XSD_NS "http://www.w3.org/2001/XMLSchema"
|
|
15
15
|
|
|
16
|
+
void xsd_capture_ics(struct leptris_xsd_schema* s,
|
|
17
|
+
LeptrisElement elem, const char* element_name);
|
|
16
18
|
extern int xsd_simple_valid(const XsdSimple* simple, const char* v);
|
|
17
19
|
extern struct xsd_validator* xsd_validator_new(
|
|
18
20
|
struct leptris_xsd_schema* s);
|
|
@@ -33,6 +35,7 @@ struct leptris_xsd_schema {
|
|
|
33
35
|
XsdElementDecl* elements;
|
|
34
36
|
XsdCm* complex_types; /* name-carrying model roots */
|
|
35
37
|
XsdTypeAttrs* type_attrs;
|
|
38
|
+
XsdElementIcs* element_ics;
|
|
36
39
|
char* target_ns;
|
|
37
40
|
void* validator; /* slice-4 parked error list */
|
|
38
41
|
char* error;
|
|
@@ -387,7 +390,10 @@ LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
|
|
|
387
390
|
d->next = s->elements;
|
|
388
391
|
s->elements = d;
|
|
389
392
|
}
|
|
393
|
+
xsd_capture_ics(s, (LeptrisElement)n, nm);
|
|
390
394
|
} else if (nm) {
|
|
395
|
+
/* slice 6: identity constraints ride the element. */
|
|
396
|
+
xsd_capture_ics(s, (LeptrisElement)n, nm);
|
|
391
397
|
/* #1592: inline anonymous type — capture under the
|
|
392
398
|
* synthesized slot and point the declaration at it. */
|
|
393
399
|
char synth[512];
|
|
@@ -482,6 +488,24 @@ LEPTRIS_API void leptris_xsd_free(LeptrisXsdSchema schema) {
|
|
|
482
488
|
struct leptris_xsd_schema* s = (struct leptris_xsd_schema*)schema;
|
|
483
489
|
if (!s) return;
|
|
484
490
|
xsd_cm_free(s->complex_types);
|
|
491
|
+
XsdElementIcs* slot = s->element_ics;
|
|
492
|
+
while (slot) {
|
|
493
|
+
XsdElementIcs* sn = slot->next;
|
|
494
|
+
XsdIc* ic = slot->constraints;
|
|
495
|
+
while (ic) {
|
|
496
|
+
XsdIc* in = ic->next;
|
|
497
|
+
free(ic->name);
|
|
498
|
+
free(ic->selector);
|
|
499
|
+
for (size_t f = 0; f < ic->field_count; f++)
|
|
500
|
+
free(ic->fields[f]);
|
|
501
|
+
free(ic->refer);
|
|
502
|
+
free(ic);
|
|
503
|
+
ic = in;
|
|
504
|
+
}
|
|
505
|
+
free(slot->element_name);
|
|
506
|
+
free(slot);
|
|
507
|
+
slot = sn;
|
|
508
|
+
}
|
|
485
509
|
XsdTypeAttrs* ta = s->type_attrs;
|
|
486
510
|
while (ta) {
|
|
487
511
|
XsdTypeAttrs* tn = ta->next;
|
|
@@ -551,6 +575,146 @@ LEPTRIS_API int leptris_xsd_simple_valid(LeptrisXsdSchema schema,
|
|
|
551
575
|
return xsd_valid_chain(s, t, lexical, 0);
|
|
552
576
|
}
|
|
553
577
|
|
|
578
|
+
/* ---- slice 6: identity-constraint capture ------------------------ */
|
|
579
|
+
|
|
580
|
+
/* The xs:selector child's @xpath (a child element, not an
|
|
581
|
+
* attribute of the constraint element itself). */
|
|
582
|
+
static char* xsd_ic_selector_xpath(LeptrisElement constraint) {
|
|
583
|
+
for (LeptrisNodeRef n =
|
|
584
|
+
leptris_node_first_child(leptris_element_as_node(constraint));
|
|
585
|
+
n; n = leptris_node_next_sibling(n)) {
|
|
586
|
+
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
587
|
+
continue;
|
|
588
|
+
LeptrisElement e = (LeptrisElement)n;
|
|
589
|
+
const char* local = NULL, *prefix = NULL, *uri = NULL;
|
|
590
|
+
leptris_element_expanded_name(e, &local, &prefix, &uri);
|
|
591
|
+
if (!local || !uri || strcmp(uri, XSD_NS) != 0) continue;
|
|
592
|
+
if (strcmp(local, "selector") == 0)
|
|
593
|
+
return xsd_strdup(leptris_element_attribute(e, "xpath"));
|
|
594
|
+
}
|
|
595
|
+
return NULL;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
void xsd_capture_ics(struct leptris_xsd_schema* s,
|
|
599
|
+
LeptrisElement elem, const char* element_name) {
|
|
600
|
+
XsdIc* head = NULL;
|
|
601
|
+
XsdIc* tail = NULL;
|
|
602
|
+
/* ICs are direct children of xs:element, or children of its
|
|
603
|
+
* inline complexType — one hop covers the second spelling. */
|
|
604
|
+
for (int hop = 0; hop < 2; hop++) {
|
|
605
|
+
for (LeptrisNodeRef n =
|
|
606
|
+
leptris_node_first_child(leptris_element_as_node(elem));
|
|
607
|
+
n; n = leptris_node_next_sibling(n)) {
|
|
608
|
+
if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
609
|
+
continue;
|
|
610
|
+
LeptrisElement e = (LeptrisElement)n;
|
|
611
|
+
const char* local = NULL, *prefix = NULL, *uri = NULL;
|
|
612
|
+
leptris_element_expanded_name(e, &local, &prefix, &uri);
|
|
613
|
+
if (!local || !uri || strcmp(uri, XSD_NS) != 0) continue;
|
|
614
|
+
if (hop == 0 &&
|
|
615
|
+
(strcmp(local, "key") == 0 ||
|
|
616
|
+
strcmp(local, "unique") == 0 ||
|
|
617
|
+
strcmp(local, "keyref") == 0)) {
|
|
618
|
+
XsdIc* ic = (XsdIc*)calloc(1, sizeof(*ic));
|
|
619
|
+
if (!ic) continue;
|
|
620
|
+
ic->kind = strcmp(local, "key") == 0 ? 0
|
|
621
|
+
: strcmp(local, "unique") == 0 ? 1
|
|
622
|
+
: 2;
|
|
623
|
+
ic->name = xsd_strdup(
|
|
624
|
+
leptris_element_attribute(e, "name"));
|
|
625
|
+
ic->selector = xsd_ic_selector_xpath(e);
|
|
626
|
+
ic->refer = xsd_strdup(
|
|
627
|
+
leptris_element_attribute(e, "refer"));
|
|
628
|
+
for (LeptrisNodeRef fc = leptris_node_first_child(
|
|
629
|
+
leptris_element_as_node(e));
|
|
630
|
+
fc && ic->field_count < 8;
|
|
631
|
+
fc = leptris_node_next_sibling(fc)) {
|
|
632
|
+
if (leptris_node_get_type(fc) !=
|
|
633
|
+
LEPTRIS_NODE_TYPE_ELEMENT)
|
|
634
|
+
continue;
|
|
635
|
+
LeptrisElement fe = (LeptrisElement)fc;
|
|
636
|
+
const char* fl = NULL, *fp = NULL, *fu = NULL;
|
|
637
|
+
leptris_element_expanded_name(fe, &fl, &fp, &fu);
|
|
638
|
+
if (!fl || !fu || strcmp(fu, XSD_NS) != 0) continue;
|
|
639
|
+
if (strcmp(fl, "field") != 0) continue;
|
|
640
|
+
ic->fields[ic->field_count++] = xsd_strdup(
|
|
641
|
+
leptris_element_attribute(fe, "xpath"));
|
|
642
|
+
}
|
|
643
|
+
if (tail)
|
|
644
|
+
tail->next = ic;
|
|
645
|
+
else
|
|
646
|
+
head = ic;
|
|
647
|
+
tail = ic;
|
|
648
|
+
} else if (hop == 0 && strcmp(local, "complexType") == 0) {
|
|
649
|
+
/* descend into the inline type's ICs */
|
|
650
|
+
for (LeptrisNodeRef ic_n = leptris_node_first_child(
|
|
651
|
+
leptris_element_as_node(e));
|
|
652
|
+
ic_n; ic_n = leptris_node_next_sibling(ic_n)) {
|
|
653
|
+
if (leptris_node_get_type(ic_n) !=
|
|
654
|
+
LEPTRIS_NODE_TYPE_ELEMENT)
|
|
655
|
+
continue;
|
|
656
|
+
LeptrisElement ie = (LeptrisElement)ic_n;
|
|
657
|
+
const char* il = NULL, *ip = NULL, *iu = NULL;
|
|
658
|
+
leptris_element_expanded_name(ie, &il, &ip, &iu);
|
|
659
|
+
if (!il || !iu || strcmp(iu, XSD_NS) != 0) continue;
|
|
660
|
+
if (strcmp(il, "key") != 0 &&
|
|
661
|
+
strcmp(il, "unique") != 0 &&
|
|
662
|
+
strcmp(il, "keyref") != 0)
|
|
663
|
+
continue;
|
|
664
|
+
XsdIc* ic = (XsdIc*)calloc(1, sizeof(*ic));
|
|
665
|
+
if (!ic) continue;
|
|
666
|
+
ic->kind = strcmp(il, "key") == 0 ? 0
|
|
667
|
+
: strcmp(il, "unique") == 0 ? 1
|
|
668
|
+
: 2;
|
|
669
|
+
ic->name = xsd_strdup(
|
|
670
|
+
leptris_element_attribute(ie, "name"));
|
|
671
|
+
ic->selector = xsd_ic_selector_xpath(ie);
|
|
672
|
+
ic->refer = xsd_strdup(
|
|
673
|
+
leptris_element_attribute(ie, "refer"));
|
|
674
|
+
for (LeptrisNodeRef fc =
|
|
675
|
+
leptris_node_first_child(
|
|
676
|
+
leptris_element_as_node(ie));
|
|
677
|
+
fc && ic->field_count < 8;
|
|
678
|
+
fc = leptris_node_next_sibling(fc)) {
|
|
679
|
+
if (leptris_node_get_type(fc) !=
|
|
680
|
+
LEPTRIS_NODE_TYPE_ELEMENT)
|
|
681
|
+
continue;
|
|
682
|
+
LeptrisElement fe = (LeptrisElement)fc;
|
|
683
|
+
const char* fl = NULL, *fp = NULL, *fu = NULL;
|
|
684
|
+
leptris_element_expanded_name(fe, &fl, &fp,
|
|
685
|
+
&fu);
|
|
686
|
+
if (!fl || !fu || strcmp(fu, XSD_NS) != 0)
|
|
687
|
+
continue;
|
|
688
|
+
if (strcmp(fl, "field") != 0) continue;
|
|
689
|
+
ic->fields[ic->field_count++] = xsd_strdup(
|
|
690
|
+
leptris_element_attribute(fe, "xpath"));
|
|
691
|
+
}
|
|
692
|
+
if (tail)
|
|
693
|
+
tail->next = ic;
|
|
694
|
+
else
|
|
695
|
+
head = ic;
|
|
696
|
+
tail = ic;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (!head) return;
|
|
702
|
+
XsdElementIcs* slot = (XsdElementIcs*)calloc(1, sizeof(*slot));
|
|
703
|
+
if (!slot) return;
|
|
704
|
+
slot->element_name = xsd_strdup(element_name);
|
|
705
|
+
slot->constraints = head;
|
|
706
|
+
slot->next = s->element_ics;
|
|
707
|
+
s->element_ics = slot;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const XsdElementIcs* xsd_find_element_ics(struct leptris_xsd_schema* s,
|
|
711
|
+
const char* element_name) {
|
|
712
|
+
if (!s || !element_name) return NULL;
|
|
713
|
+
for (XsdElementIcs* t = s->element_ics; t; t = t->next)
|
|
714
|
+
if (strcmp(t->element_name, element_name) == 0) return t;
|
|
715
|
+
return NULL;
|
|
716
|
+
}
|
|
717
|
+
|
|
554
718
|
/* ---- validator accessors (validate.c) --------------------------- */
|
|
555
719
|
|
|
556
720
|
const XsdElementDecl* xsd_find_element(struct leptris_xsd_schema* s,
|
|
@@ -40,6 +40,8 @@ extern const XsdSimple* xsd_find_simple_pub(
|
|
|
40
40
|
extern const char* xsd_target_ns(struct leptris_xsd_schema* s);
|
|
41
41
|
extern const XsdTypeAttrs* xsd_find_type_attrs(
|
|
42
42
|
struct leptris_xsd_schema* s, const char* type_name);
|
|
43
|
+
extern const XsdElementIcs* xsd_find_element_ics(
|
|
44
|
+
struct leptris_xsd_schema* s, const char* element_name);
|
|
43
45
|
|
|
44
46
|
/* ---- error list ---------------------------------------------------- */
|
|
45
47
|
|
|
@@ -248,6 +250,206 @@ static void validate_element(struct xsd_validator* v,
|
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
|
|
253
|
+
/* ---- slice 6: identity constraints -------------------------------- */
|
|
254
|
+
|
|
255
|
+
/* A key tuple: the field values of one selected node. */
|
|
256
|
+
typedef struct xsd_key_tuple {
|
|
257
|
+
char* values[8];
|
|
258
|
+
size_t count;
|
|
259
|
+
struct xsd_key_tuple* next;
|
|
260
|
+
} XsdKeyTuple;
|
|
261
|
+
|
|
262
|
+
/* All tuples collected for one constraint name, keyed by name. */
|
|
263
|
+
typedef struct xsd_key_table {
|
|
264
|
+
char* name;
|
|
265
|
+
XsdKeyTuple* tuples;
|
|
266
|
+
XsdKeyTuple* tail;
|
|
267
|
+
struct xsd_key_table* next;
|
|
268
|
+
} XsdKeyTable;
|
|
269
|
+
|
|
270
|
+
static XsdKeyTable* table_find(XsdKeyTable* head, const char* name) {
|
|
271
|
+
for (; head; head = head->next)
|
|
272
|
+
if (strcmp(head->name, name) == 0) return head;
|
|
273
|
+
return NULL;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static char* xsd_dup_str(const char* s) {
|
|
277
|
+
if (!s) return NULL;
|
|
278
|
+
size_t n = strlen(s) + 1;
|
|
279
|
+
char* out = (char*)malloc(n);
|
|
280
|
+
if (out) memcpy(out, s, n);
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static void table_add(XsdKeyTable** head, const char* name,
|
|
285
|
+
char* const* values, size_t count) {
|
|
286
|
+
XsdKeyTable* t = table_find(*head, name);
|
|
287
|
+
if (!t) {
|
|
288
|
+
t = (XsdKeyTable*)calloc(1, sizeof(*t));
|
|
289
|
+
if (!t) return;
|
|
290
|
+
size_t n = strlen(name) + 1;
|
|
291
|
+
t->name = (char*)malloc(n);
|
|
292
|
+
if (!t->name) { free(t); return; }
|
|
293
|
+
memcpy(t->name, name, n);
|
|
294
|
+
t->next = *head;
|
|
295
|
+
*head = t;
|
|
296
|
+
}
|
|
297
|
+
XsdKeyTuple* tup = (XsdKeyTuple*)calloc(1, sizeof(*tup));
|
|
298
|
+
if (!tup) return;
|
|
299
|
+
for (size_t i = 0; i < count && i < 8; i++)
|
|
300
|
+
tup->values[i] = xsd_dup_str(values[i]);
|
|
301
|
+
tup->count = count < 8 ? count : 8;
|
|
302
|
+
if (t->tail)
|
|
303
|
+
t->tail->next = tup;
|
|
304
|
+
else
|
|
305
|
+
t->tuples = tup;
|
|
306
|
+
t->tail = tup;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Evaluate one IC against the scoping element. pass1: collect
|
|
310
|
+
* key/unique tuples and enforce uniqueness (key: also non-empty
|
|
311
|
+
* fields). pass2: keyref tuples resolve against collected tables. */
|
|
312
|
+
static void validate_ics(struct xsd_validator* v,
|
|
313
|
+
struct leptris_xsd_schema* s,
|
|
314
|
+
LeptrisElement elem, const char* elem_name,
|
|
315
|
+
XsdKeyTable** tables, int pass) {
|
|
316
|
+
(void)v;
|
|
317
|
+
const XsdElementIcs* ics = xsd_find_element_ics(s, elem_name);
|
|
318
|
+
if (!ics) return;
|
|
319
|
+
extern struct leptris_document* leptris_element_get_document(
|
|
320
|
+
LeptrisElement elem);
|
|
321
|
+
LeptrisDocument doc = leptris_element_get_document(elem);
|
|
322
|
+
for (XsdIc* ic = ics->constraints; ic; ic = ic->next) {
|
|
323
|
+
int is_ref = ic->kind == 2;
|
|
324
|
+
if (pass == 1 && is_ref) continue;
|
|
325
|
+
if (pass == 2 && !is_ref) continue;
|
|
326
|
+
if (!ic->selector || !ic->name) continue;
|
|
327
|
+
LeptrisXPathResult sel =
|
|
328
|
+
leptris_xpath_eval(doc, elem, ic->selector);
|
|
329
|
+
if (!sel) continue;
|
|
330
|
+
size_t sel_count = leptris_xpath_result_count(sel);
|
|
331
|
+
for (size_t i = 0; i < sel_count; i++) {
|
|
332
|
+
LeptrisElement node = leptris_xpath_result_get(sel, i);
|
|
333
|
+
if (!node) continue;
|
|
334
|
+
/* field values: string of each field expression */
|
|
335
|
+
char* vals[8] = {0};
|
|
336
|
+
size_t nv = 0;
|
|
337
|
+
int missing = 0;
|
|
338
|
+
for (size_t f = 0; f < ic->field_count && f < 8; f++) {
|
|
339
|
+
LeptrisXPathResult fr =
|
|
340
|
+
leptris_xpath_eval(doc, node, ic->fields[f]);
|
|
341
|
+
if (!fr || leptris_xpath_result_count(fr) == 0) {
|
|
342
|
+
missing = 1;
|
|
343
|
+
if (fr) leptris_xpath_result_free(fr);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
/* String-value of the first node — attribute nodes
|
|
347
|
+
* included (leptris_xpath_result_string handles every
|
|
348
|
+
* node kind; #3526 contract). */
|
|
349
|
+
char* sv = leptris_xpath_result_string(fr);
|
|
350
|
+
leptris_xpath_result_free(fr);
|
|
351
|
+
vals[nv] = xsd_dup_str(sv ? sv : "");
|
|
352
|
+
leptris_free_string(sv);
|
|
353
|
+
nv++;
|
|
354
|
+
}
|
|
355
|
+
for (size_t k = 0; k < nv; k++)
|
|
356
|
+
if (!vals[k] || !vals[k][0]) missing = 1;
|
|
357
|
+
if (missing) {
|
|
358
|
+
if (ic->kind == 0)
|
|
359
|
+
verrf(v, "key", ic->name,
|
|
360
|
+
"a selected node is missing a field value");
|
|
361
|
+
for (size_t k = 0; k < nv; k++) free(vals[k]);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (pass == 1) {
|
|
365
|
+
/* uniqueness within this constraint */
|
|
366
|
+
XsdKeyTable* t = table_find(*tables, ic->name);
|
|
367
|
+
int dup = 0;
|
|
368
|
+
if (t) {
|
|
369
|
+
for (XsdKeyTuple* tup = t->tuples; tup;
|
|
370
|
+
tup = tup->next) {
|
|
371
|
+
int eq = tup->count == nv;
|
|
372
|
+
for (size_t k = 0; eq && k < nv; k++)
|
|
373
|
+
if (strcmp(tup->values[k], vals[k]) != 0)
|
|
374
|
+
eq = 0;
|
|
375
|
+
if (eq) { dup = 1; break; }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (dup) {
|
|
379
|
+
verrf(v, ic->kind == 0 ? "key" : "unique",
|
|
380
|
+
ic->name, "duplicate key tuple");
|
|
381
|
+
}
|
|
382
|
+
table_add(tables, ic->name, vals, nv);
|
|
383
|
+
} else {
|
|
384
|
+
/* keyref: resolve later (deferred to pass 2 end) —
|
|
385
|
+
* store under the REFER name */
|
|
386
|
+
table_add(tables, ic->refer ? ic->refer : "",
|
|
387
|
+
vals, nv);
|
|
388
|
+
}
|
|
389
|
+
for (size_t k = 0; k < nv; k++) free(vals[k]);
|
|
390
|
+
}
|
|
391
|
+
leptris_xpath_result_free(sel);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Pass 2 tail: every keyref tuple must appear in the referenced
|
|
396
|
+
* key/unique table. */
|
|
397
|
+
static void resolve_keyrefs(struct xsd_validator* v,
|
|
398
|
+
struct leptris_xsd_schema* s,
|
|
399
|
+
XsdKeyTable* all, XsdKeyTable* refs) {
|
|
400
|
+
(void)s;
|
|
401
|
+
for (XsdKeyTable* rt = refs; rt; rt = rt->next) {
|
|
402
|
+
XsdKeyTable* target = table_find(all, rt->name);
|
|
403
|
+
if (!target) continue; /* unknown refer: capture-time lax */
|
|
404
|
+
for (XsdKeyTuple* want = rt->tuples; want; want = want->next) {
|
|
405
|
+
int found = 0;
|
|
406
|
+
for (XsdKeyTuple* have = target->tuples; have;
|
|
407
|
+
have = have->next) {
|
|
408
|
+
int eq = have->count == want->count;
|
|
409
|
+
for (size_t k = 0; eq && k < want->count; k++)
|
|
410
|
+
if (strcmp(have->values[k], want->values[k]) != 0)
|
|
411
|
+
eq = 0;
|
|
412
|
+
if (eq) { found = 1; break; }
|
|
413
|
+
}
|
|
414
|
+
if (!found)
|
|
415
|
+
verrf(v, "keyref", rt->name,
|
|
416
|
+
"reference does not resolve to any key");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static void tables_free(XsdKeyTable* t) {
|
|
422
|
+
while (t) {
|
|
423
|
+
XsdKeyTable* n = t->next;
|
|
424
|
+
XsdKeyTuple* tup = t->tuples;
|
|
425
|
+
while (tup) {
|
|
426
|
+
XsdKeyTuple* tn = tup->next;
|
|
427
|
+
for (size_t k = 0; k < tup->count; k++) free(tup->values[k]);
|
|
428
|
+
free(tup);
|
|
429
|
+
tup = tn;
|
|
430
|
+
}
|
|
431
|
+
free(t->name);
|
|
432
|
+
free(t);
|
|
433
|
+
t = n;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Walk every element carrying constraints in both passes. */
|
|
438
|
+
static void ic_walk(struct xsd_validator* v, struct leptris_xsd_schema* s,
|
|
439
|
+
LeptrisElement elem, XsdKeyTable** keys,
|
|
440
|
+
XsdKeyTable** refs, int pass) {
|
|
441
|
+
const char* local = NULL;
|
|
442
|
+
leptris_element_expanded_name(elem, &local, NULL, NULL);
|
|
443
|
+
if (local) validate_ics(v, s, elem, local, pass == 1 ? keys : refs, pass);
|
|
444
|
+
for (LeptrisNodeRef c =
|
|
445
|
+
leptris_node_first_child(leptris_element_as_node(elem));
|
|
446
|
+
c; c = leptris_node_next_sibling(c)) {
|
|
447
|
+
if (leptris_node_get_type(c) != LEPTRIS_NODE_TYPE_ELEMENT)
|
|
448
|
+
continue;
|
|
449
|
+
ic_walk(v, s, (LeptrisElement)c, keys, refs, pass);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
251
453
|
struct xsd_validator* xsd_validator_new(struct leptris_xsd_schema* s) {
|
|
252
454
|
struct xsd_validator* v =
|
|
253
455
|
(struct xsd_validator*)calloc(1, sizeof(*v));
|
|
@@ -272,6 +474,17 @@ int xsd_validator_run(struct xsd_validator* v, LeptrisDocument doc) {
|
|
|
272
474
|
LeptrisElement root = leptris_document_root(doc);
|
|
273
475
|
if (!root) return -1;
|
|
274
476
|
validate_element(v, v->s, root, 0);
|
|
477
|
+
|
|
478
|
+
/* slice 6: identity constraints — collect (pass 1), resolve
|
|
479
|
+
* keyrefs (pass 2). */
|
|
480
|
+
XsdKeyTable* keys = NULL;
|
|
481
|
+
XsdKeyTable* refs = NULL;
|
|
482
|
+
ic_walk(v, v->s, root, &keys, &refs, 1);
|
|
483
|
+
ic_walk(v, v->s, root, &keys, &refs, 2);
|
|
484
|
+
resolve_keyrefs(v, v->s, keys, refs);
|
|
485
|
+
tables_free(keys);
|
|
486
|
+
tables_free(refs);
|
|
487
|
+
|
|
275
488
|
return v->count == 0 ? 1 : 0;
|
|
276
489
|
}
|
|
277
490
|
|
|
@@ -76,6 +76,26 @@ typedef struct xsd_attr_decl {
|
|
|
76
76
|
|
|
77
77
|
/* complexType name -> its attribute declarations (hash-free: a
|
|
78
78
|
* parallel list keyed the same way as complex model roots). */
|
|
79
|
+
/* ---- slice 6: identity constraints -------------------------------- */
|
|
80
|
+
|
|
81
|
+
/* xs:key | xs:unique | xs:keyref captured on an element decl. */
|
|
82
|
+
typedef struct xsd_ic {
|
|
83
|
+
char* name; /* constraint name */
|
|
84
|
+
int kind; /* 0 = key, 1 = unique, 2 = keyref */
|
|
85
|
+
char* selector; /* XPath relative to the scoping element */
|
|
86
|
+
char* fields[8]; /* XPaths relative to each selected node */
|
|
87
|
+
size_t field_count;
|
|
88
|
+
char* refer; /* keyref: the referenced constraint's name */
|
|
89
|
+
struct xsd_ic* next;
|
|
90
|
+
} XsdIc;
|
|
91
|
+
|
|
92
|
+
/* Per-declaration constraint list: entries keyed by element name. */
|
|
93
|
+
typedef struct xsd_element_ics {
|
|
94
|
+
char* element_name;
|
|
95
|
+
XsdIc* constraints;
|
|
96
|
+
struct xsd_element_ics* next;
|
|
97
|
+
} XsdElementIcs;
|
|
98
|
+
|
|
79
99
|
typedef struct xsd_type_attrs {
|
|
80
100
|
char* type_name;
|
|
81
101
|
XsdAttrDecl* attrs;
|
|
@@ -605,3 +605,122 @@ TEST(XsdDatatypes, AnonymousInlineSimpleTypeOnParticle) {
|
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
} // namespace
|
|
608
|
+
|
|
609
|
+
// ---- slice 6: identity constraints ---------------------------------
|
|
610
|
+
namespace {
|
|
611
|
+
|
|
612
|
+
const char* k_ic_schema =
|
|
613
|
+
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
|
|
614
|
+
"<xs:element name=\"catalog\">"
|
|
615
|
+
"<xs:complexType>"
|
|
616
|
+
"<xs:sequence>"
|
|
617
|
+
"<xs:element name=\"product\" maxOccurs=\"unbounded\">"
|
|
618
|
+
"<xs:complexType>"
|
|
619
|
+
"<xs:sequence>"
|
|
620
|
+
"<xs:element name=\"partNum\" type=\"xs:string\"/>"
|
|
621
|
+
"</xs:sequence>"
|
|
622
|
+
"<xs:attribute name=\"id\" type=\"xs:string\" use=\"required\"/>"
|
|
623
|
+
"</xs:complexType>"
|
|
624
|
+
"</xs:element>"
|
|
625
|
+
"</xs:sequence>"
|
|
626
|
+
"</xs:complexType>"
|
|
627
|
+
"<xs:key name=\"productId\">"
|
|
628
|
+
"<xs:selector xpath=\"product\"/>"
|
|
629
|
+
"<xs:field xpath=\"@id\"/>"
|
|
630
|
+
"</xs:key>"
|
|
631
|
+
"<xs:keyref name=\"productRef\" refer=\"productId\">"
|
|
632
|
+
"<xs:selector xpath=\"product\"/>"
|
|
633
|
+
"<xs:field xpath=\"partNum\"/>"
|
|
634
|
+
"</xs:keyref>"
|
|
635
|
+
"</xs:element>"
|
|
636
|
+
"</xs:schema>";
|
|
637
|
+
|
|
638
|
+
TEST(XsdIc, UniqueKeysValidate) {
|
|
639
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
640
|
+
LeptrisXsdSchema s =
|
|
641
|
+
leptris_xsd_compile(k_ic_schema, strlen(k_ic_schema), &st);
|
|
642
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
643
|
+
const char* xml =
|
|
644
|
+
"<catalog>"
|
|
645
|
+
"<product id=\"A1\"><partNum>A1</partNum></product>"
|
|
646
|
+
"<product id=\"B2\"><partNum>B2</partNum></product>"
|
|
647
|
+
"</catalog>";
|
|
648
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
649
|
+
ASSERT_NE(d, nullptr);
|
|
650
|
+
EXPECT_EQ(leptris_xsd_validate(s, d), 1)
|
|
651
|
+
<< (leptris_xsd_error_count(s) ? leptris_xsd_error_at(s, 0) : "");
|
|
652
|
+
leptris_document_free(d);
|
|
653
|
+
leptris_xsd_free(s);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
TEST(XsdIc, DuplicateKeyReports) {
|
|
657
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
658
|
+
LeptrisXsdSchema s =
|
|
659
|
+
leptris_xsd_compile(k_ic_schema, strlen(k_ic_schema), &st);
|
|
660
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
661
|
+
const char* xml =
|
|
662
|
+
"<catalog>"
|
|
663
|
+
"<product id=\"A1\"><partNum>A1</partNum></product>"
|
|
664
|
+
"<product id=\"A1\"><partNum>zz</partNum></product>"
|
|
665
|
+
"</catalog>";
|
|
666
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
667
|
+
ASSERT_NE(d, nullptr);
|
|
668
|
+
EXPECT_EQ(leptris_xsd_validate(s, d), 0);
|
|
669
|
+
size_t n = leptris_xsd_error_count(s);
|
|
670
|
+
int saw_dup = 0;
|
|
671
|
+
for (size_t i = 0; i < n; i++) {
|
|
672
|
+
const char* e = leptris_xsd_error_at(s, i);
|
|
673
|
+
if (e && strstr(e, "duplicate")) saw_dup = 1;
|
|
674
|
+
}
|
|
675
|
+
EXPECT_TRUE(saw_dup);
|
|
676
|
+
leptris_document_free(d);
|
|
677
|
+
leptris_xsd_free(s);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
TEST(XsdIc, UnresolvedKeyrefReports) {
|
|
681
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
682
|
+
LeptrisXsdSchema s =
|
|
683
|
+
leptris_xsd_compile(k_ic_schema, strlen(k_ic_schema), &st);
|
|
684
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
685
|
+
const char* xml =
|
|
686
|
+
"<catalog>"
|
|
687
|
+
"<product id=\"A1\"><partNum>NOPE</partNum></product>"
|
|
688
|
+
"</catalog>";
|
|
689
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
690
|
+
ASSERT_NE(d, nullptr);
|
|
691
|
+
EXPECT_EQ(leptris_xsd_validate(s, d), 0);
|
|
692
|
+
size_t n = leptris_xsd_error_count(s);
|
|
693
|
+
int saw_ref = 0;
|
|
694
|
+
for (size_t i = 0; i < n; i++) {
|
|
695
|
+
const char* e = leptris_xsd_error_at(s, i);
|
|
696
|
+
if (e && strstr(e, "keyref")) saw_ref = 1;
|
|
697
|
+
}
|
|
698
|
+
EXPECT_TRUE(saw_ref);
|
|
699
|
+
leptris_document_free(d);
|
|
700
|
+
leptris_xsd_free(s);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
TEST(XsdIc, KeyRequiresFieldValues) {
|
|
704
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
705
|
+
const char* xsd =
|
|
706
|
+
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
|
|
707
|
+
"<xs:element name=\"r\">"
|
|
708
|
+
"<xs:complexType><xs:sequence>"
|
|
709
|
+
"<xs:element name=\"item\" maxOccurs=\"unbounded\"/>"
|
|
710
|
+
"</xs:sequence></xs:complexType>"
|
|
711
|
+
"<xs:key name=\"k\">"
|
|
712
|
+
"<xs:selector xpath=\"item\"/>"
|
|
713
|
+
"<xs:field xpath=\"@id\"/>"
|
|
714
|
+
"</xs:key>"
|
|
715
|
+
"</xs:element></xs:schema>";
|
|
716
|
+
LeptrisXsdSchema s = leptris_xsd_compile(xsd, strlen(xsd), &st);
|
|
717
|
+
ASSERT_NE(s, (LeptrisXsdSchema)0);
|
|
718
|
+
const char* xml = "<r><item/><item id=\"a\"/></r>";
|
|
719
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
720
|
+
ASSERT_NE(d, nullptr);
|
|
721
|
+
EXPECT_EQ(leptris_xsd_validate(s, d), 0); /* missing field */
|
|
722
|
+
leptris_document_free(d);
|
|
723
|
+
leptris_xsd_free(s);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
} // 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.324",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.324.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|