leptris 1.9.322.0-aarch64-linux → 1.9.324.0-aarch64-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: 7cff747c994e40643b0cc1a8ac9eed34824664b6e88888195ec4a2f4191dca78
4
- data.tar.gz: bbd9e7ed63e8e24d383368399d0da9fc401e664b9377411ffaecb646486e70a3
3
+ metadata.gz: ed267566d3e55930faa65d87747fd502b922376cd3f16d6783f04ae7de3a47e6
4
+ data.tar.gz: 8184d0c615d800963d0cc817d3ede259727048e2dd17467bc2c8ef02766f9234
5
5
  SHA512:
6
- metadata.gz: dfd0aa551baa98ecdd243f7bf8ba3d88583ebb1ae1b880fad72e9337963971cd46898550b0aea73da6c6f4299d350a04f2a92f8d835f068624a3ca234c344e65
7
- data.tar.gz: 6bf7ecfe8511c5567a17643220f651fb2c560f5d8f178e1fd3af370e40f52d1bad67ad7eb102175525f9192a810872ad8a85a0f9cec0a92a8c995765e3528849
6
+ metadata.gz: 0b42f07f5f59d5002dd4743f179508b1518e56f85127479d584d7856535975f403db11af37b69c04cdfaa790dac9202bf16b21d84baeaff2cf2ab8fee27a1faf
7
+ data.tar.gz: 6b08a56371d60b846590531f510e9cd9fb86d94baa5099e9a2df302c7aade031317ab5fc58ef3ee86a7dd318d37945c66f5ba144f4b2fab5e140fc928f8f636c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.9.323.0] - 2026-10-08
2
+
3
+ ### Fixed — engine sync (libleptris 1.9.323)
4
+
5
+ - **Inline anonymous complexType/simpleType capture (#1592,
6
+ engine PR #1596)**: elements carrying an inline anonymous type —
7
+ the most common XSD spelling — previously got no content model
8
+ (instance validation silently skipped their content check;
9
+ content_valid? answered -1). Inline types capture under a
10
+ synthesized element:NAME slot now; particle text types through
11
+ the slot too. Pinned: content-model enforcement with enumerated
12
+ errors, content_valid? answering, anonymous simpleType
13
+ patterns binding.
14
+
1
15
  ## [1.9.322.0] - 2026-10-08
2
16
 
3
17
  ### Engine sync (libleptris 1.9.322)
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.322"
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.322.0"
4
+ VERSION = "1.9.324.0"
5
5
  end
@@ -533,6 +533,11 @@ class Leptris::XML::Document
533
533
  Leptris::XML::Serialization::DOCUMENT_SERIALIZE_INTO, c_ptr,
534
534
  indent: indent, no_decl: no_decl, encoding: encoding)
535
535
  end
536
+
537
+ # Nokogiri parity (leptris#1595): to_s serializes the document.
538
+ def to_s
539
+ to_xml
540
+ end
536
541
  alias_method :to_s, :to_xml
537
542
  alias_method :serialize, :to_xml
538
543
 
@@ -943,6 +943,14 @@ class Leptris::XML::Element < Leptris::XML::Node
943
943
  indent: indent, no_decl: no_decl, encoding: encoding)
944
944
  end
945
945
 
946
+ # Nokogiri parity (leptris#1595): to_s serializes the node —
947
+ # the default Object#to_s object dump leaked into migrated
948
+ # consumers (coradoc math stems). Serialization options ride
949
+ # to_xml; to_s is the no-options convenience form.
950
+ def to_s
951
+ to_xml
952
+ end
953
+
946
954
  # HTML5 serialization of this element subtree (#309 — see
947
955
  # Document#to_html for the semantics).
948
956
  def to_html
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.322
4
+ - libleptris v1.9.324
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,43 @@
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
+
24
+ ## [1.9.323] - 2026-10-08
25
+
26
+ ### Fixed
27
+
28
+ - **XSD: inline anonymous complexType/simpleType capture
29
+ (#1592, PR #1596).** Elements carrying an inline anonymous type —
30
+ arguably the most common XSD spelling — got no content model:
31
+ instance validation silently skipped their content check and
32
+ `leptris_xsd_content_valid` answered −1. Inline types now capture
33
+ under a synthesized `element:NAME` slot (collision-free — `:`
34
+ cannot appear in xs: NCName type names): top-level elements and
35
+ every particle fall back to the inline capture when the type
36
+ attribute is absent, and local simpleType particles type their
37
+ text through the slot.
38
+
39
+
40
+
3
41
  ## [1.9.322] - 2026-10-08
4
42
 
5
43
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.322
6
+ VERSION 1.9.324
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -4,6 +4,7 @@
4
4
  * grow the model (datatypes, facets, content models, identity
5
5
  * constraints) — this slice's contract is the API shape, the
6
6
  * xs:schema identity check, and the declaration enumeration. */
7
+ #include <stdio.h>
7
8
  #include <stdlib.h>
8
9
  #include <string.h>
9
10
 
@@ -12,6 +13,8 @@
12
13
 
13
14
  #define XSD_NS "http://www.w3.org/2001/XMLSchema"
14
15
 
16
+ void xsd_capture_ics(struct leptris_xsd_schema* s,
17
+ LeptrisElement elem, const char* element_name);
15
18
  extern int xsd_simple_valid(const XsdSimple* simple, const char* v);
16
19
  extern struct xsd_validator* xsd_validator_new(
17
20
  struct leptris_xsd_schema* s);
@@ -32,6 +35,7 @@ struct leptris_xsd_schema {
32
35
  XsdElementDecl* elements;
33
36
  XsdCm* complex_types; /* name-carrying model roots */
34
37
  XsdTypeAttrs* type_attrs;
38
+ XsdElementIcs* element_ics;
35
39
  char* target_ns;
36
40
  void* validator; /* slice-4 parked error list */
37
41
  char* error;
@@ -41,6 +45,20 @@ struct leptris_xsd_schema {
41
45
 
42
46
  static char* xsd_strdup(const char* s);
43
47
 
48
+ /* #1592: capture an INLINE anonymous xs:complexType / xs:simpleType
49
+ * under a synthesized name ("element:NAME" — ':' cannot appear in
50
+ * xs: NCName type names, so the slot is collision-free). Returns 1
51
+ * when a capture happened. Forward decls below; definitions later
52
+ * in this file. */
53
+ static int xsd_capture_inline(struct leptris_xsd_schema* s,
54
+ LeptrisElement elem, const char* synth);
55
+ static XsdCm* xsd_cm_new(XsdCmKind kind);
56
+ static void xsd_cm_occurrence(XsdCm* cm, LeptrisElement e);
57
+ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
58
+ LeptrisElement group, XsdCm** tail_out);
59
+ static XsdSimple* xsd_capture_simple(LeptrisElement st);
60
+
61
+
44
62
  static XsdCm* xsd_cm_new(XsdCmKind kind) {
45
63
  XsdCm* cm = (XsdCm*)calloc(1, sizeof(*cm));
46
64
  if (!cm) return NULL;
@@ -73,7 +91,45 @@ static void xsd_cm_occurrence(XsdCm* cm, LeptrisElement e) {
73
91
  /* Recursive model build over sequence | choice | all | element |
74
92
  * any children. Returns the child list head; *tail_out receives
75
93
  * the list tail for sibling chaining. */
76
- static XsdCm* xsd_cm_parse(LeptrisElement group, XsdCm** tail_out) {
94
+ /* #1592: capture an INLINE anonymous xs:complexType / xs:simpleType
95
+ * under a synthesized name ("element:NAME" — ':' cannot appear in
96
+ * xs: NCName type names, so the slot is collision-free). Returns 1
97
+ * when a capture happened. */
98
+ static int xsd_capture_inline(struct leptris_xsd_schema* s,
99
+ LeptrisElement elem, const char* synth) {
100
+ for (LeptrisNodeRef n =
101
+ leptris_node_first_child(leptris_element_as_node(elem));
102
+ n; n = leptris_node_next_sibling(n)) {
103
+ if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
104
+ continue;
105
+ LeptrisElement e = (LeptrisElement)n;
106
+ const char* local = NULL, *prefix = NULL, *uri = NULL;
107
+ leptris_element_expanded_name(e, &local, &prefix, &uri);
108
+ if (!local || !uri || strcmp(uri, XSD_NS) != 0) continue;
109
+ if (strcmp(local, "complexType") == 0) {
110
+ XsdCm* root = xsd_cm_new(XSD_CM_SEQ);
111
+ if (!root) return 0;
112
+ root->name = xsd_strdup(synth);
113
+ root->first_child = xsd_cm_parse(s, e, NULL);
114
+ root->next = s->complex_types;
115
+ s->complex_types = root;
116
+ return 1;
117
+ }
118
+ if (strcmp(local, "simpleType") == 0) {
119
+ XsdSimple* captured = xsd_capture_simple(e);
120
+ if (!captured) return 0;
121
+ free(captured->name);
122
+ captured->name = xsd_strdup(synth);
123
+ captured->next = s->simple_types;
124
+ s->simple_types = captured;
125
+ return 1;
126
+ }
127
+ }
128
+ return 0;
129
+ }
130
+
131
+ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
132
+ LeptrisElement group, XsdCm** tail_out) {
77
133
  XsdCm* head = NULL;
78
134
  XsdCm* tail = NULL;
79
135
  for (LeptrisNodeRef n =
@@ -92,7 +148,7 @@ static XsdCm* xsd_cm_parse(LeptrisElement group, XsdCm** tail_out) {
92
148
  cm = xsd_cm_new(strcmp(local, "sequence") == 0 ? XSD_CM_SEQ
93
149
  : strcmp(local, "choice") == 0 ? XSD_CM_CHOICE
94
150
  : XSD_CM_ALL);
95
- if (cm) cm->first_child = xsd_cm_parse(e, NULL);
151
+ if (cm) cm->first_child = xsd_cm_parse(s, e, NULL);
96
152
  } else if (strcmp(local, "element") == 0) {
97
153
  cm = xsd_cm_new(XSD_CM_ELEMENT);
98
154
  if (cm) {
@@ -100,8 +156,17 @@ static XsdCm* xsd_cm_parse(LeptrisElement group, XsdCm** tail_out) {
100
156
  leptris_element_attribute(e, "name");
101
157
  if (!nm) nm = leptris_element_attribute(e, "ref");
102
158
  cm->name = xsd_strdup(nm);
103
- cm->type = xsd_strdup(
104
- leptris_element_attribute(e, "type"));
159
+ const char* pty =
160
+ leptris_element_attribute(e, "type");
161
+ if (pty) {
162
+ cm->type = xsd_strdup(pty);
163
+ } else if (s && nm) {
164
+ /* #1592: inline anonymous type on the particle */
165
+ char synth[512];
166
+ snprintf(synth, sizeof(synth), "element:%s", nm);
167
+ if (xsd_capture_inline(s, e, synth))
168
+ cm->type = xsd_strdup(synth);
169
+ }
105
170
  }
106
171
  } else if (strcmp(local, "any") == 0) {
107
172
  cm = xsd_cm_new(XSD_CM_ANY);
@@ -140,7 +205,9 @@ static char* xsd_strdup(const char* s) {
140
205
  /* Capture one xs:simpleType's restriction model (slice 2). */
141
206
  static XsdSimple* xsd_capture_simple(LeptrisElement st) {
142
207
  const char* name = leptris_element_attribute(st, "name");
143
- if (!name) return NULL; /* anonymous types ride their element */
208
+ /* Anonymous types ride their element — allowed: the caller
209
+ * renames the captured entry into its synthesized slot
210
+ * (#1592). */
144
211
 
145
212
  XsdSimple* simple = (XsdSimple*)calloc(1, sizeof(*simple));
146
213
  if (!simple) return NULL;
@@ -323,6 +390,24 @@ LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
323
390
  d->next = s->elements;
324
391
  s->elements = d;
325
392
  }
393
+ xsd_capture_ics(s, (LeptrisElement)n, nm);
394
+ } else if (nm) {
395
+ /* slice 6: identity constraints ride the element. */
396
+ xsd_capture_ics(s, (LeptrisElement)n, nm);
397
+ /* #1592: inline anonymous type — capture under the
398
+ * synthesized slot and point the declaration at it. */
399
+ char synth[512];
400
+ snprintf(synth, sizeof(synth), "element:%s", nm);
401
+ if (xsd_capture_inline(s, (LeptrisElement)n, synth)) {
402
+ XsdElementDecl* d =
403
+ (XsdElementDecl*)calloc(1, sizeof(*d));
404
+ if (d) {
405
+ d->name = xsd_strdup(nm);
406
+ d->type = xsd_strdup(synth);
407
+ d->next = s->elements;
408
+ s->elements = d;
409
+ }
410
+ }
326
411
  }
327
412
  } else if (strcmp(local, "complexType") == 0 && uri &&
328
413
  !strcmp(uri, XSD_NS)) {
@@ -334,7 +419,7 @@ LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
334
419
  if (root) {
335
420
  root->name = xsd_strdup(nm);
336
421
  root->first_child =
337
- xsd_cm_parse((LeptrisElement)n, NULL);
422
+ xsd_cm_parse(s, (LeptrisElement)n, NULL);
338
423
  root->next = s->complex_types;
339
424
  s->complex_types = root;
340
425
  }
@@ -403,6 +488,24 @@ LEPTRIS_API void leptris_xsd_free(LeptrisXsdSchema schema) {
403
488
  struct leptris_xsd_schema* s = (struct leptris_xsd_schema*)schema;
404
489
  if (!s) return;
405
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
+ }
406
509
  XsdTypeAttrs* ta = s->type_attrs;
407
510
  while (ta) {
408
511
  XsdTypeAttrs* tn = ta->next;
@@ -472,6 +575,146 @@ LEPTRIS_API int leptris_xsd_simple_valid(LeptrisXsdSchema schema,
472
575
  return xsd_valid_chain(s, t, lexical, 0);
473
576
  }
474
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
+
475
718
  /* ---- validator accessors (validate.c) --------------------------- */
476
719
 
477
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;
@@ -517,3 +517,210 @@ TEST(XsdValidate, NullTolerant) {
517
517
  }
518
518
 
519
519
  } // namespace
520
+
521
+ // ---- #1592: inline anonymous types ---------------------------------
522
+ namespace {
523
+
524
+ const char* k_anon_schema =
525
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
526
+ "<xs:element name=\"book\">"
527
+ "<xs:complexType>"
528
+ "<xs:sequence>"
529
+ "<xs:element name=\"title\" type=\"xs:token\"/>"
530
+ "<xs:element name=\"author\" type=\"xs:token\"/>"
531
+ "</xs:sequence>"
532
+ "</xs:complexType>"
533
+ "</xs:element>"
534
+ "</xs:schema>";
535
+
536
+ TEST(XsdContent, AnonymousInlineComplexTypeCaptured) {
537
+ LeptrisStatus st = LEPTRIS_OK;
538
+ LeptrisXsdSchema s =
539
+ leptris_xsd_compile(k_anon_schema, strlen(k_anon_schema), &st);
540
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
541
+ /* the issue's exact symptom: content_valid answered -1 */
542
+ const char* good[] = {"title", "author"};
543
+ EXPECT_EQ(leptris_xsd_content_valid(s, "book", good, NULL, 2), 1);
544
+ const char* bad[] = {"author", "author", "isbn"};
545
+ EXPECT_EQ(leptris_xsd_content_valid(s, "book", bad, NULL, 3), 0);
546
+ const char* miss[] = {"author"};
547
+ EXPECT_EQ(leptris_xsd_content_valid(s, "book", miss, NULL, 1), 0);
548
+ leptris_xsd_free(s);
549
+ }
550
+
551
+ TEST(XsdValidate, AnonymousInlineValidatesEndToEnd) {
552
+ LeptrisStatus st = LEPTRIS_OK;
553
+ LeptrisXsdSchema s =
554
+ leptris_xsd_compile(k_anon_schema, strlen(k_anon_schema), &st);
555
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
556
+ const char* good_x = "<book><title>t</title><author>a</author></book>";
557
+ LeptrisDocument good =
558
+ leptris_parse_string(good_x, strlen(good_x), &st);
559
+ ASSERT_NE(good, nullptr);
560
+ EXPECT_EQ(leptris_xsd_validate(s, good), 1);
561
+ leptris_document_free(good);
562
+ /* the issue's instance: missing title, out-of-model isbn */
563
+ const char* bad_x =
564
+ "<book><author/><author/><isbn>9</isbn></book>";
565
+ LeptrisDocument bad = leptris_parse_string(bad_x, strlen(bad_x), &st);
566
+ ASSERT_NE(bad, nullptr);
567
+ EXPECT_EQ(leptris_xsd_validate(s, bad), 0);
568
+ EXPECT_GE(leptris_xsd_error_count(s), 1u);
569
+ leptris_document_free(bad);
570
+ leptris_xsd_free(s);
571
+ }
572
+
573
+ TEST(XsdDatatypes, AnonymousInlineSimpleTypeOnParticle) {
574
+ LeptrisStatus st = LEPTRIS_OK;
575
+ const char* xsd =
576
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
577
+ "<xs:complexType name=\"t\">"
578
+ "<xs:sequence>"
579
+ "<xs:element name=\"score\">"
580
+ "<xs:simpleType>"
581
+ "<xs:restriction base=\"xs:integer\">"
582
+ "<xs:minInclusive value=\"1\"/>"
583
+ "</xs:restriction>"
584
+ "</xs:simpleType>"
585
+ "</xs:element>"
586
+ "</xs:sequence></xs:complexType>"
587
+ "<xs:element name=\"game\" type=\"t\"/>"
588
+ "</xs:schema>";
589
+ LeptrisXsdSchema s = leptris_xsd_compile(xsd, strlen(xsd), &st);
590
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
591
+ const char* good_x = "<game><score>9</score></game>";
592
+ LeptrisDocument good = leptris_parse_string(good_x, strlen(good_x), &st);
593
+ ASSERT_NE(good, nullptr);
594
+ EXPECT_EQ(leptris_xsd_validate(s, good), 1);
595
+ leptris_document_free(good);
596
+ const char* bad_x = "<game><score>0</score></game>";
597
+ LeptrisDocument bad = leptris_parse_string(bad_x, strlen(bad_x), &st);
598
+ ASSERT_NE(bad, nullptr);
599
+ EXPECT_EQ(leptris_xsd_validate(s, bad), 0);
600
+ leptris_document_free(bad);
601
+ /* the synthesized slot resolves directly too */
602
+ EXPECT_EQ(leptris_xsd_simple_valid(s, "element:score", "9"), 1);
603
+ EXPECT_EQ(leptris_xsd_simple_valid(s, "element:score", "0"), 0);
604
+ leptris_xsd_free(s);
605
+ }
606
+
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.322",
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.322.0
4
+ version: 1.9.324.0
5
5
  platform: aarch64-linux
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