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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d10f6b3b909939e8ac8b34a658edc1dbcfda8e2fe00b2718a6a173e4f8edb7a
4
- data.tar.gz: 4f029c7a9e91e1f9fd894a73b4f0bf27e1508edf7bde95ccf54a659bde63a646
3
+ metadata.gz: 20ca01027af6d2d9c7dd54b359eb38fb8243682d85499c82fa021f22605718f6
4
+ data.tar.gz: 67293f668204144634cac5a732f48376f218ac225378f1139809900ccd459c06
5
5
  SHA512:
6
- metadata.gz: 4d47a9c1683ebbd4d3e5e014e1287d1d0d5b32e75da0eea029ada1692b0d13daf7a2394a5fca647947127118035c93cadc88b317bded1da7138fb54d51f78100
7
- data.tar.gz: 773ceaf67ef802ba7202952f7a2a39368b9572c2ca6888d94e386ba191735afc2963cc94722cc730a9f3fd366724e54ecf51084c0a8665d27cdd3451ec073e0e
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.323"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.323.0"
4
+ VERSION = "1.9.324.0"
5
5
  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.323
4
+ - libleptris v1.9.324
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.323
6
+ VERSION 1.9.324
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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.323",
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.323.0
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-08 00:00:00.000000000 Z
11
+ date: 2026-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi