leptris 1.9.384.0-arm-linux → 1.9.386.0-arm-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/lib/leptris/version.rb +1 -1
  4. data/lib/leptris/xml/document.rb +11 -1
  5. data/lib/leptris/xml/element.rb +15 -10
  6. data/lib/leptris/xml/ffi.rb +42 -0
  7. data/lib/leptris/xml/xsd.rb +35 -0
  8. data/lib/libleptris.so +0 -0
  9. data/vendor-src/README.md +1 -1
  10. data/vendor-src/libleptris/CHANGELOG.md +135 -0
  11. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  12. data/vendor-src/libleptris/src/include/leptris/descriptor.h +16 -0
  13. data/vendor-src/libleptris/src/include/leptris.h +77 -0
  14. data/vendor-src/libleptris/src/leptris/descriptor/plan.c +82 -0
  15. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +23 -2
  16. data/vendor-src/libleptris/src/leptris/dom/element_query.c +201 -0
  17. data/vendor-src/libleptris/src/leptris/memory/pool.c +27 -0
  18. data/vendor-src/libleptris/src/leptris/memory/pool.h +5 -0
  19. data/vendor-src/libleptris/src/leptris/xpath/evaluator_axes.c +15 -0
  20. data/vendor-src/libleptris/src/leptris/xsd/compile.c +232 -15
  21. data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +180 -46
  22. data/vendor-src/libleptris/src/leptris/xsd/validate.c +69 -22
  23. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +10 -0
  24. data/vendor-src/libleptris/test/abi/test_descriptor.cpp +201 -0
  25. data/vendor-src/libleptris/test/dom/test_document_children.cpp +256 -0
  26. data/vendor-src/libleptris/test/dom/test_dom.cpp +263 -0
  27. data/vendor-src/libleptris/test/xsd/libxml2-cases/schema_set/f.xsd +13 -0
  28. data/vendor-src/libleptris/test/xsd/libxml2-cases/schema_set/g.xsd +9 -0
  29. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +293 -1
  30. data/vendor-src/libleptris/vcpkg.json +1 -1
  31. metadata +4 -2
@@ -1616,6 +1616,165 @@ LEPTRIS_API const char* leptris_element_namespace_decl_uri(
1616
1616
  return uri;
1617
1617
  }
1618
1618
 
1619
+ /* #1874: pre-order DFS over the subtree, one row per (element with
1620
+ * declarations, declaration). The explicit stack (elements + the
1621
+ * borrowed path-name chain) keeps arbitrarily deep trees off the C
1622
+ * stack. First pass counts rows and blob bytes; the second fills —
1623
+ * the two passes walk identically, so sizing and filling agree. */
1624
+ LEPTRIS_API size_t leptris_element_namespace_declarations_snapshot(
1625
+ LeptrisElement elem,
1626
+ leptris_ns_decl_row* rows, size_t rows_cap, size_t* out_row_count,
1627
+ char* blob, size_t blob_cap, size_t* blob_used_out) {
1628
+ size_t row_count = 0;
1629
+ size_t blob_used = 0;
1630
+
1631
+ /* Dynamic DFS stack: element + depth. The path-name chain rides
1632
+ * a parallel array of borrowed name pointers indexed by depth. */
1633
+ struct dfs_entry {
1634
+ LeptrisElement e;
1635
+ uint32_t depth;
1636
+ };
1637
+ size_t stack_cap = 64, names_cap = 64;
1638
+ struct dfs_entry* stack = (struct dfs_entry*)malloc(
1639
+ stack_cap * sizeof(*stack));
1640
+ const char** names = (const char**)malloc(names_cap * sizeof(*names));
1641
+ if (!stack || !names) {
1642
+ free(stack);
1643
+ free(names);
1644
+ if (out_row_count) *out_row_count = 0;
1645
+ if (blob_used_out) *blob_used_out = 0;
1646
+ return (size_t)-1;
1647
+ }
1648
+ size_t sp = 0;
1649
+
1650
+ if (elem) {
1651
+ stack[sp].e = elem;
1652
+ stack[sp].depth = 0;
1653
+ sp++;
1654
+ }
1655
+
1656
+ while (sp > 0) {
1657
+ sp--;
1658
+ LeptrisElement e = stack[sp].e;
1659
+ uint32_t depth = stack[sp].depth;
1660
+
1661
+ /* Path name for this element (depth 0 writes nothing); DFS
1662
+ * visits the parent first, so ancestors are recorded. */
1663
+ if (depth > 0) {
1664
+ if (depth >= names_cap) {
1665
+ size_t nc = names_cap * 2;
1666
+ while (nc <= depth) nc *= 2;
1667
+ const char** grown = (const char**)realloc(
1668
+ names, nc * sizeof(*names));
1669
+ if (!grown) goto oom;
1670
+ names = grown;
1671
+ names_cap = nc;
1672
+ }
1673
+ names[depth] = leptris_element_name(e);
1674
+ }
1675
+
1676
+ /* Count (or emit) this element's declarations. */
1677
+ size_t decls = leptris_element_namespace_count(e);
1678
+ for (size_t i = 0; i < decls; i++) {
1679
+ const char* prefix = NULL;
1680
+ const char* uri = NULL;
1681
+ element_namespace_decl_at(e, i, &prefix, &uri);
1682
+ size_t prefix_len = prefix ? strlen(prefix) : 0;
1683
+ size_t uri_len = uri ? strlen(uri) : 0;
1684
+ /* Row bytes: the path names + prefix + href, the
1685
+ * prefix/href NUL-terminated like the attributes_snapshot
1686
+ * lane blobs (bindings read C strings straight out). */
1687
+ size_t row_bytes = (prefix ? prefix_len + 1 : 0) +
1688
+ uri_len + 1;
1689
+ for (uint32_t d = 1; d <= depth; d++) {
1690
+ const char* nm = names[d] ? names[d] : "";
1691
+ row_bytes += strlen(nm) + 1;
1692
+ }
1693
+ if (rows) {
1694
+ if (row_count >= rows_cap ||
1695
+ blob_used + row_bytes > blob_cap) {
1696
+ free(stack);
1697
+ free(names);
1698
+ return (size_t)-1;
1699
+ }
1700
+ leptris_ns_decl_row* r = &rows[row_count];
1701
+ r->depth = depth;
1702
+ r->name_count = depth;
1703
+ /* Path names: depth components, ancestor-first,
1704
+ * each NUL-terminated. */
1705
+ r->names_off = (uint32_t)blob_used;
1706
+ for (uint32_t d = 1; d <= depth; d++) {
1707
+ const char* nm = names[d] ? names[d] : "";
1708
+ size_t nl = strlen(nm) + 1;
1709
+ memcpy(blob + blob_used, nm, nl);
1710
+ blob_used += nl;
1711
+ }
1712
+ if (prefix) {
1713
+ r->prefix_off = (uint32_t)blob_used;
1714
+ r->prefix_len = (uint32_t)prefix_len;
1715
+ memcpy(blob + blob_used, prefix, prefix_len);
1716
+ blob_used += prefix_len;
1717
+ blob[blob_used++] = '\0';
1718
+ } else {
1719
+ r->prefix_off = 0xFFFFFFFFu;
1720
+ r->prefix_len = 0;
1721
+ }
1722
+ r->href_off = (uint32_t)blob_used;
1723
+ r->href_len = (uint32_t)uri_len;
1724
+ if (uri_len) {
1725
+ memcpy(blob + blob_used, uri, uri_len);
1726
+ blob_used += uri_len;
1727
+ }
1728
+ blob[blob_used++] = '\0';
1729
+ } else {
1730
+ /* Sizing pass: measure the same bytes the fill pass
1731
+ * writes (those advance blob_used inline). */
1732
+ blob_used += row_bytes;
1733
+ }
1734
+ row_count++;
1735
+ }
1736
+
1737
+ /* Push children in REVERSE so the pop order is document
1738
+ * order. */
1739
+ size_t nkids = 0;
1740
+ for (LeptrisElement c = leptris_element_first_child_any(e); c;
1741
+ c = leptris_element_next_sibling_any(c)) {
1742
+ nkids++;
1743
+ }
1744
+ if (sp + nkids > stack_cap) {
1745
+ size_t nc = stack_cap * 2;
1746
+ while (nc < sp + nkids) nc *= 2;
1747
+ struct dfs_entry* grown = (struct dfs_entry*)realloc(
1748
+ stack, nc * sizeof(*stack));
1749
+ if (!grown) goto oom;
1750
+ stack = grown;
1751
+ stack_cap = nc;
1752
+ }
1753
+ /* Collect children into the stack tail backwards. */
1754
+ size_t w = sp + nkids;
1755
+ for (LeptrisElement c = leptris_element_first_child_any(e); c;
1756
+ c = leptris_element_next_sibling_any(c)) {
1757
+ w--;
1758
+ stack[w].e = c;
1759
+ stack[w].depth = depth + 1;
1760
+ }
1761
+ sp += nkids;
1762
+ }
1763
+
1764
+ free(stack);
1765
+ free(names);
1766
+ if (out_row_count) *out_row_count = row_count;
1767
+ if (blob_used_out) *blob_used_out = blob_used;
1768
+ return 0;
1769
+
1770
+ oom:
1771
+ free(stack);
1772
+ free(names);
1773
+ if (out_row_count) *out_row_count = 0;
1774
+ if (blob_used_out) *blob_used_out = 0;
1775
+ return (size_t)-1;
1776
+ }
1777
+
1619
1778
  /* ============================================================================
1620
1779
  * Namespace mutation (issue #186).
1621
1780
  *
@@ -1790,3 +1949,45 @@ LEPTRIS_API size_t leptris_element_attributes_raw(
1790
1949
  }
1791
1950
  return written;
1792
1951
  }
1952
+
1953
+ /* ---- #1855: address-Integer batch faces ------------------------------ */
1954
+
1955
+ LEPTRIS_API size_t leptris_element_children_batch(
1956
+ uintptr_t elem_addr,
1957
+ uintptr_t* child_addrs, uint8_t* child_kinds, size_t cap) {
1958
+ if (!elem_addr) return 0;
1959
+ LeptrisElement e = (LeptrisElement)elem_addr;
1960
+ size_t count = 0;
1961
+ /* NODE-level walk: the batch carries every child kind (text,
1962
+ * comment, PI, CDATA included) — first_child_any is
1963
+ * element-only. */
1964
+ for (LeptrisNode* c = leptris_node_first_child((LeptrisNode*)e); c;
1965
+ c = leptris_node_next_sibling(c)) {
1966
+ if (child_addrs && child_kinds && count < cap) {
1967
+ child_addrs[count] = (uintptr_t)c;
1968
+ child_kinds[count] = (uint8_t)c->type;
1969
+ }
1970
+ count++;
1971
+ }
1972
+ /* Undersized cap: write nothing and hand back the count so the
1973
+ * caller sizes and retries (the snapshot-face discipline). */
1974
+ if (count > cap) return count;
1975
+ return count;
1976
+ }
1977
+
1978
+ LEPTRIS_API int leptris_node_name_compare(uintptr_t node_addr,
1979
+ const char* name, size_t len) {
1980
+ if (!node_addr) return -2;
1981
+ const char* node_name = "";
1982
+ if (leptris_node_get_type((LeptrisNodeRef)node_addr) ==
1983
+ LEPTRIS_NODE_TYPE_ELEMENT) {
1984
+ node_name = leptris_element_get_name((LeptrisElement)node_addr);
1985
+ if (!node_name) node_name = "";
1986
+ }
1987
+ size_t node_len = strlen(node_name);
1988
+ size_t min = node_len < len ? node_len : len;
1989
+ int rc = strncmp(node_name, name ? name : "", min);
1990
+ if (rc != 0) return rc < 0 ? -1 : 1;
1991
+ if (node_len == len) return 0;
1992
+ return node_len < len ? -1 : 1;
1993
+ }
@@ -340,6 +340,33 @@ LeptrisMemoryPool* leptris_pool_create_with_page_size(size_t page_size) {
340
340
  return pool;
341
341
  }
342
342
 
343
+ int leptris_pool_contains(const LeptrisMemoryPool* pool,
344
+ const void* addr) {
345
+ if (!pool || !addr) return 0;
346
+ for (const MemoryPage* p = pool->first_page; p; p = p->next) {
347
+ if (p->busy_size <= sizeof(struct memory_page)) continue;
348
+ const char* lo = p->data;
349
+ const char* hi =
350
+ p->data + (p->busy_size - sizeof(struct memory_page));
351
+ if ((const char*)addr >= lo && (const char*)addr < hi) return 1;
352
+ }
353
+ /* Post-parse growth (arena extensions, oversized requests) lives
354
+ * in tracked malloc blocks outside the span — still pool-owned,
355
+ * still freed by leptris_pool_destroy. Callers use this predicate
356
+ * to answer "did this pointer die with the document?"
357
+ * (leptris#1866). */
358
+ for (const LeptrisBigAlloc* b = pool->first_big_alloc; b; b = b->next) {
359
+ if ((const char*)addr >= (const char*)b->ptr &&
360
+ (const char*)addr < (const char*)b->ptr + b->size) return 1;
361
+ }
362
+ if (pool->arena && pool->arena->base) {
363
+ if ((const char*)addr >= pool->arena->base &&
364
+ (const char*)addr < pool->arena->base + pool->arena->used)
365
+ return 1;
366
+ }
367
+ return 0;
368
+ }
369
+
343
370
  void leptris_pool_destroy(LeptrisMemoryPool* pool) {
344
371
  if (!pool) return;
345
372
 
@@ -172,6 +172,11 @@ int leptris_pool_is_arena_backed(const LeptrisMemoryPool* pool);
172
172
  * @param pool Pool to destroy (NULL is safe)
173
173
  */
174
174
  void leptris_pool_destroy(LeptrisMemoryPool* pool);
175
+ /* leptris#1866: nonzero when addr lies within the pool's handed-out
176
+ * memory — any page's live span, the arena bump region, or a tracked
177
+ * extension block (freed with the pool like the rest). */
178
+ int leptris_pool_contains(const LeptrisMemoryPool* pool,
179
+ const void* addr);
175
180
 
176
181
  /* Release this thread's recycler pages back to the global pool
177
182
  * (leptris_thread_cleanup). */
@@ -755,6 +755,21 @@ XPathNodeSet* apply_axis(XPathContext* ctx, LeptrisNode* node,
755
755
  return xpath_nodeset_new();
756
756
  }
757
757
 
758
+ /* Attribute inputs: the parent axis resolves through the OWNER
759
+ * element (XPath 1.0 §2.2 — `./@id/..` names the attribute's
760
+ * element). The element-typed dispatch below would read the
761
+ * parent field in element form off the attribute struct and
762
+ * return empty (leptris#1866, the standoc svg-uniqueids face). */
763
+ if (axis_id == XPATH_AXIS_PARENT && node && node->type == LEPTRIS_NODE_ATTRIBUTE) {
764
+ LeptrisElement owner = ((LeptrisAttributeNode*)node)->owner;
765
+ XPathNodeSet* result = xpath_nodeset_new();
766
+ if (!result) return result;
767
+ if (owner && matches_node_test(ctx, (LeptrisNode*)owner, test)) {
768
+ xpath_nodeset_add(result, owner);
769
+ }
770
+ return result;
771
+ }
772
+
758
773
  LeptrisElement elem = (LeptrisElement)node;
759
774
  switch (axis_id) {
760
775
  case XPATH_AXIS_DESCENDANT:
@@ -20,7 +20,7 @@
20
20
  void xsd_capture_ics(struct leptris_xsd_schema* s,
21
21
  LeptrisElement elem, const char* element_name);
22
22
  extern int xsd_simple_valid(const XsdSimple* simple, const char* v,
23
- const char* terminal_base);
23
+ const char* terminal_base, int chain_is_list);
24
24
  extern struct xsd_validator* xsd_validator_new(
25
25
  struct leptris_xsd_schema* s);
26
26
  extern void xsd_validator_free(struct xsd_validator* v);
@@ -179,6 +179,7 @@ static void xsd_cm_free(XsdCm* cm) {
179
179
  free(cm->type);
180
180
  free(cm->ns);
181
181
  free(cm->ext_base);
182
+ free(cm->ext_base_ns);
182
183
  free(cm->any_ns);
183
184
  free(cm->not_qname);
184
185
  free(cm->any_unit_tns);
@@ -292,6 +293,9 @@ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
292
293
  cm->process_skip =
293
294
  (pc && (strcmp(pc, "skip") == 0 ||
294
295
  strcmp(pc, "lax") == 0)) ? 1 : 0;
296
+ cm->pc_level =
297
+ (pc && strcmp(pc, "skip") == 0) ? 2
298
+ : (pc && strcmp(pc, "lax") == 0) ? 1 : 0;
295
299
  }
296
300
  } else {
297
301
  continue; /* annotation & friends */
@@ -565,6 +569,25 @@ static int xsd_item_valid(struct leptris_xsd_schema* s,
565
569
  return 1;
566
570
  }
567
571
 
572
+ /* #1882: does the base chain resolve to a LIST type? A restriction
573
+ * OF a list keeps deriv RESTRICT, but its length facets still count
574
+ * items — the value space is the list's. */
575
+ static int xsd_chain_is_list(struct leptris_xsd_schema* s,
576
+ const XsdSimple* t) {
577
+ int depth = 0;
578
+ while (t && depth < 32) {
579
+ if (t->deriv == XSD_SIMPLE_LIST) return 1;
580
+ if (!t->base) return 0;
581
+ if (strncmp(t->base, "xs:", 3) == 0)
582
+ return strcmp(t->base, "xs:IDREFS") == 0 ||
583
+ strcmp(t->base, "xs:NMTOKENS") == 0 ||
584
+ strcmp(t->base, "xs:ENTITIES") == 0;
585
+ t = xsd_find_simple(s, t->base);
586
+ depth++;
587
+ }
588
+ return 0;
589
+ }
590
+
568
591
  /* The chain's terminal builtin (NULL = none resolved): the length
569
592
  * family needs it to count octets for the binary bases
570
593
  * (leptris-ruby#434). */
@@ -621,7 +644,7 @@ static int xsd_deriv_valid(struct leptris_xsd_schema* s,
621
644
  return 1;
622
645
  return 0;
623
646
  }
624
- return xsd_simple_valid(t, v, xsd_chain_terminal(s, t));
647
+ return xsd_simple_valid(t, v, xsd_chain_terminal(s, t), 0);
625
648
  }
626
649
 
627
650
  static int xsd_valid_chain(struct leptris_xsd_schema* s,
@@ -633,7 +656,8 @@ static int xsd_valid_chain(struct leptris_xsd_schema* s,
633
656
  }
634
657
  if (t->deriv != XSD_SIMPLE_RESTRICT)
635
658
  return xsd_deriv_valid(s, t, v, depth);
636
- return xsd_simple_valid(t, v, xsd_chain_terminal(s, t));
659
+ return xsd_simple_valid(t, v, xsd_chain_terminal(s, t),
660
+ xsd_chain_is_list(s, t));
637
661
  }
638
662
 
639
663
  static void xsd_set_error(struct leptris_xsd_schema* s, const char* msg) {
@@ -2030,7 +2054,13 @@ static void xsd_resolve_extension_bases(struct leptris_xsd_schema* s) {
2030
2054
  progressed = 0;
2031
2055
  for (XsdCm* c = s->complex_types; c; c = c->next) {
2032
2056
  if (!c->ext_base) continue;
2033
- XsdCm* base = xsd_find_complex_ns(s, c->ext_base, c->ns);
2057
+ /* #1881: the fold resolves in the BASE's namespace —
2058
+ * recorded at capture from the base QName's prefix —
2059
+ * falling back to the deriver's ns (same-unit refs,
2060
+ * where the old behavior lives). */
2061
+ const char* bns = c->ext_base_ns ? c->ext_base_ns : c->ns;
2062
+ XsdCm* base = xsd_find_complex_ns(s, c->ext_base, bns);
2063
+ if (!base) base = xsd_find_complex_ns(s, c->ext_base, c->ns);
2034
2064
  if (!base || base->ext_base) continue;
2035
2065
  XsdCm* copied = xsd_cm_copy_list(base->first_child);
2036
2066
  if (copied) {
@@ -2050,7 +2080,9 @@ static void xsd_resolve_extension_bases(struct leptris_xsd_schema* s) {
2050
2080
  }
2051
2081
  }
2052
2082
  const XsdTypeAttrs* bt =
2053
- xsd_find_type_attrs_ns(s, c->ext_base, c->ns);
2083
+ xsd_find_type_attrs_ns(s, c->ext_base, bns);
2084
+ if (!bt)
2085
+ bt = xsd_find_type_attrs_ns(s, c->ext_base, c->ns);
2054
2086
  if (bt) {
2055
2087
  XsdTypeAttrs* mine = NULL;
2056
2088
  for (XsdTypeAttrs* t = s->type_attrs; t; t = t->next)
@@ -2094,6 +2126,8 @@ static void xsd_resolve_extension_bases(struct leptris_xsd_schema* s) {
2094
2126
  }
2095
2127
  free(c->ext_base);
2096
2128
  c->ext_base = NULL;
2129
+ free(c->ext_base_ns);
2130
+ c->ext_base_ns = NULL;
2097
2131
  progressed = 1;
2098
2132
  }
2099
2133
  }
@@ -2159,8 +2193,43 @@ static void xsd_visited_add(struct leptris_xsd_schema* s,
2159
2193
  s->visited[s->visited_len++] = dup;
2160
2194
  }
2161
2195
 
2196
+ static XsdAttrDecl* xsd_capture_attr_rows(
2197
+ struct leptris_xsd_schema* s, LeptrisElement container, int depth);
2198
+ /* #1880: top-level attributeGroup definitions are captured in a
2199
+ * PRE-PASS — the symbol space is order-free, but the ref expansion
2200
+ * in xsd_capture_attr_rows looks up s->attr_groups at capture time,
2201
+ * so a group declared AFTER the referencing type used to vanish. */
2202
+ static void xsd_capture_attr_group_def(struct leptris_xsd_schema* s,
2203
+ LeptrisElement n,
2204
+ const char* nm) {
2205
+ XsdAttrGroupDef* g = (XsdAttrGroupDef*)calloc(1, sizeof(*g));
2206
+ if (!g) return;
2207
+ g->name = xsd_strdup(nm);
2208
+ g->attrs = xsd_capture_attr_rows(s, n, 0);
2209
+ g->next = s->attr_groups;
2210
+ s->attr_groups = g;
2211
+ }
2212
+
2162
2213
  static void xsd_capture_root(struct leptris_xsd_schema* s,
2163
2214
  LeptrisElement root) {
2215
+ /* pre-pass: every top-level attributeGroup def, document order */
2216
+ for (LeptrisNodeRef n =
2217
+ leptris_node_first_child(leptris_element_as_node(root));
2218
+ n; n = leptris_node_next_sibling(n)) {
2219
+ if (leptris_node_get_type(n) != LEPTRIS_NODE_TYPE_ELEMENT)
2220
+ continue;
2221
+ const char* local = NULL;
2222
+ const char* uri = NULL;
2223
+ leptris_element_expanded_name((LeptrisElement)n, &local,
2224
+ NULL, &uri);
2225
+ if (!local || strcmp(local, "attributeGroup") != 0 ||
2226
+ !uri || strcmp(uri, XSD_NS) != 0)
2227
+ continue;
2228
+ const char* nm = leptris_element_attribute((LeptrisElement)n,
2229
+ "name");
2230
+ if (!nm) continue;
2231
+ xsd_capture_attr_group_def(s, (LeptrisElement)n, nm);
2232
+ }
2164
2233
  for (LeptrisNodeRef n =
2165
2234
  leptris_node_first_child(leptris_element_as_node(root));
2166
2235
  n; n = leptris_node_next_sibling(n)) {
@@ -2371,16 +2440,13 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
2371
2440
  } else if (strcmp(local, "attributeGroup") == 0 && uri && !strcmp(uri, XSD_NS)) {
2372
2441
  const char* nm =
2373
2442
  leptris_element_attribute((LeptrisElement)n, "name");
2443
+ /* the #1880 pre-pass already captured it — a second
2444
+ * registration would shadow the first */
2374
2445
  if (nm) {
2375
- XsdAttrGroupDef* g =
2376
- (XsdAttrGroupDef*)calloc(1, sizeof(*g));
2377
- if (g) {
2378
- g->name = xsd_strdup(nm);
2379
- g->attrs = xsd_capture_attr_rows(
2380
- s, (LeptrisElement)n, 0);
2381
- g->next = s->attr_groups;
2382
- s->attr_groups = g;
2383
- }
2446
+ XsdAttrGroupDef* seen = s->attr_groups;
2447
+ for (; seen; seen = seen->next)
2448
+ if (strcmp(seen->name, nm) == 0) break;
2449
+ if (!seen) xsd_capture_attr_group_def(s, (LeptrisElement)n, nm);
2384
2450
  }
2385
2451
  } else if (strcmp(local, "group") == 0 && uri && !strcmp(uri, XSD_NS)) {
2386
2452
  const char* nm =
@@ -2569,6 +2635,7 @@ static XsdCm* xsd_cm_copy_list(const XsdCm* head) {
2569
2635
  d->any_ns = xsd_strdup(src->any_ns);
2570
2636
  d->any_unit_tns = xsd_strdup(src->any_unit_tns);
2571
2637
  d->process_skip = src->process_skip;
2638
+ d->pc_level = src->pc_level;
2572
2639
  d->min = src->min;
2573
2640
  d->max = src->max;
2574
2641
  d->first_child = xsd_cm_copy_list(src->first_child);
@@ -2662,6 +2729,8 @@ static XsdCm* xsd_capture_any(LeptrisElement any_child) {
2662
2729
  (pc && (strcmp(pc, "skip") == 0 || strcmp(pc, "lax") == 0))
2663
2730
  ? 1
2664
2731
  : 0;
2732
+ w->pc_level = (pc && strcmp(pc, "skip") == 0) ? 2
2733
+ : (pc && strcmp(pc, "lax") == 0) ? 1 : 0;
2665
2734
  w->min = 0;
2666
2735
  w->max = -1;
2667
2736
  return w;
@@ -2746,7 +2815,21 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
2746
2815
  continue;
2747
2816
  const char* base_raw =
2748
2817
  leptris_element_attribute(dq, "base");
2749
- const char* base = xsd_local_name(base_raw);
2818
+ /* #1881: resolve the base QName against the
2819
+ * extension element's in-scope declarations — the
2820
+ * resolved namespace rides the deferral so the
2821
+ * post-capture fold can cross units (g:foo1 extends
2822
+ * f:foo). */
2823
+ char buri[256], blocal[256];
2824
+ buri[0] = 0;
2825
+ blocal[0] = 0;
2826
+ if (base_raw)
2827
+ xsd_resolve_qname(dq, base_raw, buri, sizeof(buri),
2828
+ blocal, sizeof(blocal));
2829
+ const char* base =
2830
+ base_raw ? (blocal[0] ? blocal
2831
+ : xsd_local_name(base_raw))
2832
+ : NULL;
2750
2833
  XsdCm* base_root =
2751
2834
  (base && strncmp(base, "xs:", 3) != 0)
2752
2835
  ? xsd_find_complex_ns(s, base, s->target_ns)
@@ -2771,6 +2854,9 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
2771
2854
  strcmp(dl, "extension") == 0) {
2772
2855
  free(root->ext_base);
2773
2856
  root->ext_base = xsd_strdup(base);
2857
+ free(root->ext_base_ns);
2858
+ root->ext_base_ns =
2859
+ buri[0] ? xsd_strdup(buri) : NULL;
2774
2860
  }
2775
2861
  if (strcmp(dl, "extension") == 0) {
2776
2862
  /* base content first, derived content after */
@@ -3578,3 +3664,134 @@ LEPTRIS_API const char* leptris_xsd_error(LeptrisXsdSchema schema) {
3578
3664
  if (!schema) return NULL;
3579
3665
  return ((struct leptris_xsd_schema*)schema)->error;
3580
3666
  }
3667
+
3668
+ /* leptris#1881: schema-set compilation — every listed document's
3669
+ * declarations merge into ONE schema. The per-document pipeline is
3670
+ * the single-compile one (checks, per-unit tns, capture); the
3671
+ * visited-set is seeded with every member path first so an import
3672
+ * chain back to a set member does not recapture it, and extension
3673
+ * bases resolve ONCE after all units are in (type references
3674
+ * resolve against the whole schema, the leptris-ruby#440 law). */
3675
+ LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile_set(
3676
+ const char* const* paths, size_t count, LeptrisStatus* status) {
3677
+ if (status) *status = LEPTRIS_OK;
3678
+ if (!paths || count == 0) {
3679
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3680
+ return NULL;
3681
+ }
3682
+ struct leptris_xsd_schema* s =
3683
+ (struct leptris_xsd_schema*)calloc(1, sizeof(*s));
3684
+ if (!s) {
3685
+ if (status) *status = LEPTRIS_ERROR_MEMORY;
3686
+ return NULL;
3687
+ }
3688
+ const char* main_tns = NULL;
3689
+ for (size_t i = 0; i < count; i++) {
3690
+ const char* path = paths[i];
3691
+ if (!path || !*path) {
3692
+ xsd_set_error(s, "schema set member path is empty");
3693
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3694
+ return s;
3695
+ }
3696
+ size_t len = 0;
3697
+ char* text = xsd_read_file(path, &len);
3698
+ if (!text) {
3699
+ xsd_set_error(s, "schema set member cannot be read");
3700
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3701
+ return s;
3702
+ }
3703
+ const char* slash = strrchr(path, '/');
3704
+ const char* bslash = strrchr(path, '\\');
3705
+ if (bslash && (!slash || bslash > slash)) slash = bslash;
3706
+ char* base_dir = NULL;
3707
+ if (slash) {
3708
+ size_t n = (size_t)(slash - path);
3709
+ if (n == 0) n = 1;
3710
+ base_dir = (char*)malloc(n + 1);
3711
+ if (base_dir) {
3712
+ memcpy(base_dir, path, n);
3713
+ base_dir[n] = 0;
3714
+ }
3715
+ } else {
3716
+ base_dir = xsd_strdup(".");
3717
+ }
3718
+
3719
+ LeptrisDocument doc =
3720
+ leptris_parse_string(text, len, status);
3721
+ free(text);
3722
+ if (!doc) {
3723
+ xsd_set_error(s,
3724
+ "schema set member is not well-formed XML");
3725
+ free(base_dir);
3726
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3727
+ return s;
3728
+ }
3729
+ LeptrisElement root = leptris_document_root(doc);
3730
+ if (!root) {
3731
+ xsd_set_error(s, "schema set member has no root element");
3732
+ leptris_document_free(doc);
3733
+ free(base_dir);
3734
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3735
+ return s;
3736
+ }
3737
+ const char* local = NULL;
3738
+ const char* uri = NULL;
3739
+ leptris_element_expanded_name(root, &local, NULL, &uri);
3740
+ if (!local || strcmp(local, "schema") != 0 ||
3741
+ (uri && strcmp(uri, XSD_NS) != 0)) {
3742
+ xsd_set_error(s, "schema set member root is not xs:schema");
3743
+ leptris_document_free(doc);
3744
+ free(base_dir);
3745
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3746
+ return s;
3747
+ }
3748
+ const char* doc_tns =
3749
+ leptris_element_attribute(root, "targetNamespace");
3750
+ char* saved = s->base_dir;
3751
+ s->base_dir = base_dir;
3752
+ if (i == 0) {
3753
+ main_tns = doc_tns;
3754
+ s->target_ns = xsd_strdup(doc_tns);
3755
+ }
3756
+ /* Per-unit tns during checks + capture; the visited-set
3757
+ * seeds each member so import chains close (the import in
3758
+ * g.xsd would otherwise re-capture f.xsd mid-set). */
3759
+ {
3760
+ char* saved_ns = s->target_ns;
3761
+ s->target_ns = xsd_strdup(doc_tns);
3762
+ char canon[4096];
3763
+ xsd_path_normalize(path, canon, sizeof(canon));
3764
+ /* An earlier member's import may have captured this
3765
+ * document already (order [importer, imported]) — the
3766
+ * visited check keeps each unit to one capture. */
3767
+ if (xsd_visited_has(s, canon)) {
3768
+ free(s->target_ns);
3769
+ s->target_ns = saved_ns;
3770
+ leptris_document_free(doc);
3771
+ free(base_dir);
3772
+ s->base_dir = saved;
3773
+ continue;
3774
+ }
3775
+ xsd_visited_add(s, canon);
3776
+ int bad = xsd_check_structure(s, root, 0) != 0 ||
3777
+ xsd_check_derivation(s, root) != 0 ||
3778
+ xsd_check_refs(s, root) != 0 ||
3779
+ xsd_check_upa(s, root) != 0 ||
3780
+ xsd_check_include_tns(s, root) != 0;
3781
+ if (!bad) xsd_capture_root(s, root);
3782
+ free(s->target_ns);
3783
+ s->target_ns = saved_ns;
3784
+ if (bad) {
3785
+ leptris_document_free(doc);
3786
+ free(base_dir);
3787
+ if (status) *status = LEPTRIS_ERROR_PARSE;
3788
+ return s;
3789
+ }
3790
+ }
3791
+ leptris_document_free(doc);
3792
+ free(base_dir);
3793
+ s->base_dir = saved;
3794
+ }
3795
+ xsd_resolve_extension_bases(s);
3796
+ return s;
3797
+ }