leptris 1.9.334.1 → 1.9.336.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: a2ce64e718bb9df81b15dc1f2cdb30678e21de904adbeae1e94bb587ffb5ccb1
4
- data.tar.gz: 40eb5ec70f4d5a303e71bd26c1787028ff0841821ba7fe7009abe5bbbc09391c
3
+ metadata.gz: e4c62c67ac4f9ae3950cf00eddaf50348cf3aaf623f86a7d5962ed61f39b0a42
4
+ data.tar.gz: 023d10d1004e4c592f8c4becaa99bbf46ac6a997ffa013b727b281304d9dd9d6
5
5
  SHA512:
6
- metadata.gz: 204d14fd5b43b060571e7d7a67559adf717032da76b2ea27746ffd85d57ac9148bf0afdf17f40e9de6f263eeed501d2f1b7357bb1a5f194b6121b82ebe458a96
7
- data.tar.gz: ae0ee8c6a2b3484685ea1648e514c7fa8ef593d60f0a51b9ae95e2b6823347e21a35ac557428e89c8d349415b0febe6f5f9519d436fd03574ca7655d6e02a12f
6
+ metadata.gz: 04ae4b5411227a6cf3c66b85f2f88afb51138e6c9b4d0cb1dd171ab06c6be8425b5526e411712d7b485eefae7e8d068cfcf7698f3c2a05c94f18bd3c4168256e
7
+ data.tar.gz: 462c5abd3145f642fda1fa8e763ce403e675f5290580ea31aac8b88f11fd116b33addf4442abdfe4a2dc13ef8196527dadbfb3127aa76ef3c159ff0111379220
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.9.335.0] - 2026-10-11
2
+
3
+ ### Fixed — engine sync (libleptris 1.9.335)
4
+
5
+ - **The #414 wave (#1626, engine PR #1627)**: cross-namespace
6
+ `xs:import` declarations merge under a compiling schema's own
7
+ targetNamespace (the wml-2010 chain shape — previously invisible),
8
+ prefixed QName type references (`type="w:CT_Border"`) resolve
9
+ with `xs:`/`xsd:` builtin spellings canonized (closing a silent
10
+ gap where `xsd:`-spelled types were untyped), and content-model
11
+ failures NAME the offending child once (`(at 'right')`) — a
12
+ misordered child no longer cascades anonymously down the
13
+ ancestors. All three pinned with minimal cases.
14
+
1
15
  ## [1.9.334.1] - 2026-10-10
2
16
 
3
17
  ### Performance
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.334"
11
+ LIBLEPTRIS_VERSION = "1.9.336"
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.334.1"
4
+ VERSION = "1.9.336.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.334
4
+ - libleptris v1.9.336
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,51 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.336] - 2026-10-10
4
+
5
+ ### Fixed
6
+
7
+ - **plan_result_build members emit in op order on every platform
8
+ (leptris#1625, PR #1629).** The serializer orders children by
9
+ byte position with an unstable qsort; builder-created values
10
+ carry no position, so the tie fell to unspecified libc behavior
11
+ and collection members reversed on Windows (both x64 and
12
+ arm64). Builder values now take a dense insertion rank before
13
+ push — fixing an off-by-one in the element branch besides — and
14
+ the comparator breaks position ties on it, making the order
15
+ total and platform-independent. Walk output is undisturbed (the
16
+ walk's own ranks are dense already). The binding's round-trip
17
+ spec can flip to strict member order once a release carries
18
+ this.
19
+
20
+
21
+
22
+ ## [1.9.335] - 2026-10-10
23
+
24
+ ### Fixed
25
+
26
+ - **XSD: cross-namespace imports, prefixed type refs, named
27
+ content errors (#1626, PR #1627).** `xs:import` with a foreign
28
+ targetNamespace merges its declarations — the previous gate
29
+ skipped the normal multi-namespace shape (wml-2010 imports were
30
+ invisible when the compiling schema carried its own
31
+ targetNamespace). Prefixed QName references
32
+ (`type="w:CT_Border"`) resolve by local name at every capture
33
+ site, and the `xs:`/`xsd:` builtin spellings canonize —
34
+ closing a pre-existing gap where `xsd:`-spelled types were
35
+ silently untyped. Content-model failures name the offending
36
+ child once (`children do not match the content model
37
+ (at 'insideH')`) via a greedy expected-next match with
38
+ `minOccurs="0"` optionals skippable — one misordered child no
39
+ longer cascades anonymously down the ancestors (the reported
40
+ ~175x amplification is a bounded single report).
41
+ - **interleaved lane: compute record depths before sizing the
42
+ replay stacks (leptris#1577, PR #1624).** The sizing scan read
43
+ the still-zero-filled depth array, bound the replay to the
44
+ in-struct 256-slot buffers, and every document deeper than
45
+ DP_MAX_DEPTH overflowed them under a raised cap.
46
+
47
+
48
+
3
49
  ## [1.9.334] - 2026-10-10
4
50
 
5
51
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.334
6
+ VERSION 1.9.336
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -1344,6 +1344,11 @@ static int dp_kid_pos_cmp(const void* a, const void* b) {
1344
1344
  (const struct leptris_plan_result* const*)b;
1345
1345
  if ((*ka)->position < (*kb)->position) return -1;
1346
1346
  if ((*ka)->position > (*kb)->position) return 1;
1347
+ /* #1625: position ties (builder nodes, offset-less rows) fall
1348
+ * to the dense insertion rank - qsort is not stable, and an
1349
+ * unspecified tie reordered members per platform libc. */
1350
+ if ((*ka)->order_index < (*kb)->order_index) return -1;
1351
+ if ((*ka)->order_index > (*kb)->order_index) return 1;
1347
1352
  return 0;
1348
1353
  }
1349
1354
 
@@ -1586,12 +1591,16 @@ LEPTRIS_API LeptrisPlanResult leptris_plan_result_build(
1586
1591
  return NULL;
1587
1592
  }
1588
1593
  root = v;
1589
- } else if (!dp_value_push(stack[sp - 1], v)) {
1590
- dp_result_free_rec(v);
1591
- goto oom;
1594
+ } else {
1595
+ /* rank BEFORE push (kid_count is post-increment) -
1596
+ * every value needs one: the serializer's qsort
1597
+ * breaks position ties on it (#1625) */
1598
+ v->order_index = (uint32_t)stack[sp - 1]->kid_count;
1599
+ if (!dp_value_push(stack[sp - 1], v)) {
1600
+ dp_result_free_rec(v);
1601
+ goto oom;
1602
+ }
1592
1603
  }
1593
- v->order_index = (uint32_t)
1594
- (sp == 0 ? 0 : stack[sp - 1]->kid_count);
1595
1604
  stack[sp++] = v;
1596
1605
  } else if (op->kind == LEPTRIS_PLAN_OP_SCALAR) {
1597
1606
  if (sp == 0) {
@@ -1609,6 +1618,7 @@ LEPTRIS_API LeptrisPlanResult leptris_plan_result_build(
1609
1618
  if (!dp_value_set_str(v, op->value ? op->value : "",
1610
1619
  op->value ? op->value_len : 0))
1611
1620
  dp_value_set_str(v, "", 0);
1621
+ v->order_index = (uint32_t)stack[sp - 1]->kid_count;
1612
1622
  if (!dp_value_push(stack[sp - 1], v)) {
1613
1623
  dp_result_free_rec(v);
1614
1624
  goto oom;
@@ -64,15 +64,26 @@ typedef struct {
64
64
  char* end;
65
65
  LeptrisMemoryPool* pool;
66
66
  struct leptris_document* doc;
67
- LeptrisElement open_stack[DP_MAX_DEPTH];
67
+ LeptrisElement open_stack_buf[DP_MAX_DEPTH];
68
68
  /* Round 10: journal slot of each open element's elem_pos entry —
69
69
  * the close path updates by slot (O(1)) instead of hashing. */
70
- int pos_slot_stack[DP_MAX_DEPTH];
70
+ int pos_slot_stack_buf[DP_MAX_DEPTH];
71
71
  /* TODO 155 Phase C: per-depth last-child cache. Replaces the
72
72
  * last_child_off field on element. last_child_stack[i] holds
73
73
  * the most recently wired child of open_stack[i], or NULL when
74
74
  * no child has been wired yet at that depth. */
75
- LeptrisNode* last_child_stack[DP_MAX_DEPTH];
75
+ LeptrisNode* last_child_stack_buf[DP_MAX_DEPTH];
76
+ /* Depth-stack indirection (leptris#1577): the in-struct buffers
77
+ * serve the default DP_MAX_DEPTH cap; a configured max depth
78
+ * above that (public leptris_set_max_depth / parse options)
79
+ * switches the pointers to one heap block sized for the cap.
80
+ * The open-tag guard honors the configured depth — with the
81
+ * arrays fixed at 256, any deeper document wrote past them. */
82
+ LeptrisElement* open_stack;
83
+ int* pos_slot_stack;
84
+ LeptrisNode** last_child_stack;
85
+ size_t stack_cap;
86
+ void* stack_heap;
76
87
  int depth;
77
88
  LeptrisElement root;
78
89
  /* Document children (issue #580): [prolog..., root, epilog...]
@@ -195,6 +206,36 @@ typedef struct {
195
206
  LeptrisDTD* dtd;
196
207
  } DParser;
197
208
 
209
+ /* Depth stacks (leptris#1577): the default cap stays on the in-struct
210
+ * buffers (zero allocation); a larger configured cap gets one
211
+ * aligned heap block. Returns 0 on OOM (pointers left NULL). */
212
+ static int dp_stacks_init(DParser* p, size_t cap) {
213
+ if (cap <= DP_MAX_DEPTH) {
214
+ p->open_stack = p->open_stack_buf;
215
+ p->pos_slot_stack = p->pos_slot_stack_buf;
216
+ p->last_child_stack = p->last_child_stack_buf;
217
+ p->stack_cap = DP_MAX_DEPTH;
218
+ p->stack_heap = NULL;
219
+ return 1;
220
+ }
221
+ size_t a = (cap * sizeof(LeptrisElement) + 15u) & ~(size_t)15u;
222
+ size_t b = (cap * sizeof(int) + 15u) & ~(size_t)15u;
223
+ size_t c = cap * sizeof(LeptrisNode*);
224
+ unsigned char* blk = (unsigned char*)malloc(a + b + c);
225
+ if (!blk) return 0;
226
+ p->open_stack = (LeptrisElement*)blk;
227
+ p->pos_slot_stack = (int*)(blk + a);
228
+ p->last_child_stack = (LeptrisNode**)(blk + a + b);
229
+ p->stack_cap = cap;
230
+ p->stack_heap = blk;
231
+ return 1;
232
+ }
233
+
234
+ static void dp_stacks_release(DParser* p) {
235
+ free(p->stack_heap);
236
+ p->stack_heap = NULL;
237
+ }
238
+
198
239
  /* ---- #1125: immutable-buffer logs --------------------------------
199
240
  *
200
241
  * The zero-copy parse NUL-terminates names/values in place. To keep
@@ -1551,6 +1592,12 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
1551
1592
  p.end = buf + len;
1552
1593
  p.pool = pool;
1553
1594
  p.doc = doc;
1595
+ {
1596
+ extern LEPTRIS_THREAD_LOCAL int g_leptris_max_depth;
1597
+ size_t dcap = g_leptris_max_depth > 0
1598
+ ? (size_t)g_leptris_max_depth : (size_t)DP_MAX_DEPTH;
1599
+ if (!dp_stacks_init(&p, dcap)) goto fail;
1600
+ }
1554
1601
  p.skip_flags = skip_flags;
1555
1602
  p.depth = 0;
1556
1603
  p.root = NULL;
@@ -2311,6 +2358,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
2311
2358
  * ends the parse byte-identical to the input. (Runs after the
2312
2359
  * encoding/version strdups above, which read the buffer.) */
2313
2360
 
2361
+ dp_stacks_release(&p);
2314
2362
  return doc;
2315
2363
 
2316
2364
  fail:
@@ -2372,6 +2420,7 @@ fail:
2372
2420
  * pool_destroy above. Don't LEPTRIS_FREE(doc) (TODO 154). */
2373
2421
  /* Round 8: scratch/pristine are arena-carved — pool_destroy
2374
2422
  * above already reclaimed them. */
2423
+ dp_stacks_release(&p);
2375
2424
  return NULL;
2376
2425
  }
2377
2426
 
@@ -2446,7 +2495,35 @@ static LEPTRIS_ALWAYS_INLINE size_t il_name_end(const char* s,
2446
2495
  return (size_t)(p - s);
2447
2496
  }
2448
2497
 
2498
+ /* leptris#1577: the IL scan's depth stacks sized to the configured
2499
+ * max depth (fixed 256 before — same overflow class as the classic
2500
+ * lane). The wrapper owns the block so every body exit is covered. */
2501
+ static int il_stacks_init(IlCtx* c, size_t cap) {
2502
+ if (cap <= LEPTRIS_IL_MAX_DEPTH) {
2503
+ c->open = c->open_buf;
2504
+ c->last_child = c->last_child_buf;
2505
+ c->stack_heap = NULL;
2506
+ return 1;
2507
+ }
2508
+ unsigned char* blk = (unsigned char*)malloc(
2509
+ 2u * cap * sizeof(uint32_t));
2510
+ if (!blk) return 0;
2511
+ c->open = (uint32_t*)blk;
2512
+ c->last_child = (uint32_t*)(blk + cap * sizeof(uint32_t));
2513
+ c->stack_heap = blk;
2514
+ return 1;
2515
+ }
2516
+
2517
+ static void il_stacks_release(IlCtx* c) {
2518
+ free(c->stack_heap);
2519
+ c->stack_heap = NULL;
2520
+ }
2521
+
2449
2522
  int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2523
+ uint32_t* root_out, size_t* trail_off,
2524
+ size_t* trail_len);
2525
+
2526
+ static int il_scan_body(char* s, size_t len, int drop_ws, IlCtx* c,
2450
2527
  uint32_t* root_out, size_t* trail_off,
2451
2528
  size_t* trail_len) {
2452
2529
  const char* send = s + len;
@@ -2682,6 +2759,19 @@ int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2682
2759
  }
2683
2760
  }
2684
2761
 
2762
+ int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2763
+ uint32_t* root_out, size_t* trail_off,
2764
+ size_t* trail_len) {
2765
+ extern LEPTRIS_THREAD_LOCAL int g_leptris_max_depth;
2766
+ size_t dcap = g_leptris_max_depth > 0
2767
+ ? (size_t)g_leptris_max_depth : (size_t)LEPTRIS_IL_MAX_DEPTH;
2768
+ if (!il_stacks_init(c, dcap)) return 0;
2769
+ int ok = il_scan_body(s, len, drop_ws, c, root_out, trail_off,
2770
+ trail_len);
2771
+ il_stacks_release(c);
2772
+ return ok;
2773
+ }
2774
+
2685
2775
 
2686
2776
  /* Post-pass: build the public tree from the streams, replaying the
2687
2777
  * classic lane's exact stores. Returns NULL on OOM (caller frees). */
@@ -2791,11 +2881,28 @@ static struct leptris_document* dp_il_build(
2791
2881
  if (!smap || !rdepth || !lc) {
2792
2882
  free(smap); free(rdepth); free(lc); goto oom_pool;
2793
2883
  }
2884
+ /* Depth-stack indirection (leptris#1577): record depths are not
2885
+ * bounded by DP_MAX_DEPTH here — the replay indexes
2886
+ * last_child_stack by them. Size the stacks to the document. */
2887
+ /* Depth computation FIRST: the sizing scan below reads
2888
+ * rdepth — computed here, before the max is taken (the sizing
2889
+ * pass originally ran on the zero-filled array and sized the
2890
+ * stacks to 1, so every deep replay overflowed the in-struct
2891
+ * buffers — the exact class this PR fixes). */
2892
+ for (size_t i = 0; i < c->nrec; i++) {
2893
+ rdepth[i] = (c->recs[i].parent == IL_NONE)
2894
+ ? 0 : rdepth[c->recs[i].parent] + 1;
2895
+ }
2896
+ uint32_t il_max_d = 0;
2897
+ for (size_t i = 0; i < c->nrec; i++) {
2898
+ if (rdepth[i] > il_max_d) il_max_d = rdepth[i];
2899
+ }
2900
+ if (!dp_stacks_init(&pp, (size_t)il_max_d + 1)) {
2901
+ free(smap); free(rdepth); free(lc); goto oom_pool;
2902
+ }
2794
2903
 
2795
2904
  for (size_t i = 0; i < c->nrec; i++) {
2796
2905
  IlRec* r = &c->recs[i];
2797
- rdepth[i] = (r->parent == IL_NONE)
2798
- ? 0 : rdepth[r->parent] + 1;
2799
2906
  if (r->kind == 0) {
2800
2907
  LeptrisElement e = &eblk[nelem++];
2801
2908
  e->base.type = LEPTRIS_NODE_TYPE_ELEMENT;
@@ -3015,10 +3122,14 @@ static struct leptris_document* dp_il_build(
3015
3122
  }
3016
3123
 
3017
3124
  leptris_root_doc_register(doc->root, doc);
3125
+ dp_stacks_release(&pp);
3018
3126
  free(smap); free(rdepth); free(lc);
3019
3127
  return doc;
3020
3128
 
3021
3129
  oom_pool:
3130
+ /* Reached only at/after the replay's DParser init — the init
3131
+ * failure path leaves stack_heap NULL, so the release is safe. */
3132
+ dp_stacks_release(&pp);
3022
3133
  leptris_pool_destroy(pool);
3023
3134
  free(scratch);
3024
3135
  free(pristine);
@@ -43,8 +43,16 @@ typedef struct IlAttr {
43
43
  typedef struct IlCtx {
44
44
  IlRec* recs; size_t nrec, crec;
45
45
  IlAttr* attrs; size_t nattr, cattr;
46
- uint32_t open[LEPTRIS_IL_MAX_DEPTH];
47
- uint32_t last_child[LEPTRIS_IL_MAX_DEPTH];
46
+ /* Depth stacks (leptris#1577): the fixed buffers serve the
47
+ * default 256 cap; a configured max depth above that switches
48
+ * the pointers to one heap block. Owned by leptris_il_scan —
49
+ * initialized at entry, released at every exit; callers never
50
+ * touch these directly. */
51
+ uint32_t open_buf[LEPTRIS_IL_MAX_DEPTH];
52
+ uint32_t last_child_buf[LEPTRIS_IL_MAX_DEPTH];
53
+ uint32_t* open;
54
+ uint32_t* last_child;
55
+ void* stack_heap;
48
56
  int depth;
49
57
  } IlCtx;
50
58
 
@@ -88,6 +88,37 @@ static XsdAssert* xsd_capture_assert(struct leptris_xsd_schema* s,
88
88
  LeptrisElement e);
89
89
  static XsdCm* xsd_capture_any(LeptrisElement any_child);
90
90
  static char* xsd_capture_any_attr_ns(LeptrisElement container);
91
+
92
+ /* #1626: prefixed QName refs (type="w:CT_Border") resolve by local
93
+ * name — the engine's tables key on local names; the prefix maps
94
+ * to a namespace the name-based resolution folds away. Returns the
95
+ * local part after the colon when present, else the argument. */
96
+ static char xsd_qname_buf[8][512];
97
+ static int xsd_qname_slot;
98
+
99
+ static const char* xsd_local_name(const char* qname) {
100
+ if (!qname) return NULL;
101
+ const char* colon = strchr(qname, ':');
102
+ if (!colon) return qname;
103
+ /* xs:/xsd: are builtin references — canonize to "xs:local" so
104
+ * text_valid's dispatch matches both spellings (xsd:-spelled
105
+ * types were silently untyped before #1626). */
106
+ size_t pl = (size_t)(colon - qname);
107
+ if ((pl == 2 && strncmp(qname, "xs", 2) == 0) ||
108
+ (pl == 3 && strncmp(qname, "xsd", 3) == 0)) {
109
+ if (pl == 2) return qname;
110
+ char* dst = xsd_qname_buf[xsd_qname_slot++ & 7];
111
+ dst[0] = 'x';
112
+ dst[1] = 's';
113
+ dst[2] = ':';
114
+ snprintf(dst + 3, sizeof(xsd_qname_buf[0]) - 3, "%s",
115
+ colon + 1);
116
+ return dst;
117
+ }
118
+ /* a foreign prefix maps to a namespace; the name-based tables
119
+ * resolve by local name */
120
+ return colon + 1;
121
+ }
91
122
  static void xsd_capture_alternatives(struct leptris_xsd_schema* s,
92
123
  LeptrisElement decl_elem,
93
124
  XsdElementDecl* d);
@@ -193,12 +224,14 @@ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
193
224
  if (cm) {
194
225
  const char* nm =
195
226
  leptris_element_attribute(e, "name");
196
- if (!nm) nm = leptris_element_attribute(e, "ref");
227
+ if (!nm)
228
+ nm = xsd_local_name(
229
+ leptris_element_attribute(e, "ref"));
197
230
  cm->name = xsd_strdup(nm);
198
231
  const char* pty =
199
232
  leptris_element_attribute(e, "type");
200
233
  if (pty) {
201
- cm->type = xsd_strdup(pty);
234
+ cm->type = xsd_strdup(xsd_local_name(pty));
202
235
  } else if (s && nm) {
203
236
  /* #1592: inline anonymous type on the particle */
204
237
  char synth[512];
@@ -212,8 +245,8 @@ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
212
245
  * forward references need no capture order */
213
246
  cm = xsd_cm_new(XSD_CM_GROUP_REF);
214
247
  if (cm)
215
- cm->name = xsd_strdup(
216
- leptris_element_attribute(e, "ref"));
248
+ cm->name = xsd_strdup(xsd_local_name(
249
+ leptris_element_attribute(e, "ref")));
217
250
  } else if (strcmp(local, "any") == 0) {
218
251
  cm = xsd_cm_new(XSD_CM_ANY);
219
252
  if (cm) {
@@ -273,7 +306,8 @@ static XsdSimple* xsd_capture_simple(LeptrisElement st) {
273
306
  if (!local) continue;
274
307
  if (strcmp(local, "list") == 0) {
275
308
  simple->deriv = XSD_SIMPLE_LIST;
276
- const char* it = leptris_element_attribute(e, "itemType");
309
+ const char* it = xsd_local_name(
310
+ leptris_element_attribute(e, "itemType"));
277
311
  if (it && *it) {
278
312
  simple->item_type = xsd_strdup(it);
279
313
  } else {
@@ -346,8 +380,8 @@ static XsdSimple* xsd_capture_simple(LeptrisElement st) {
346
380
  simple->member_defs[simple->member_def_count++] = def;
347
381
  }
348
382
  } else if (strcmp(local, "restriction") == 0) {
349
- simple->base =
350
- xsd_strdup(leptris_element_attribute(e, "base"));
383
+ simple->base = xsd_strdup(xsd_local_name(
384
+ leptris_element_attribute(e, "base")));
351
385
  for (LeptrisNodeRef f = leptris_node_first_child(
352
386
  leptris_element_as_node(e));
353
387
  f; f = leptris_node_next_sibling(f)) {
@@ -694,9 +728,16 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
694
728
  /* same-namespace merges and no-namespace
695
729
  * chameleon includes; foreign-namespace
696
730
  * imports stay lax at this tier */
731
+ /* xs:include: same-namespace (or
732
+ * chameleon — the no-tns form adopts ours).
733
+ * xs:import: foreign namespaces are the
734
+ * point — merge regardless (#1626: wml-2010
735
+ * shape imports were skipped when the
736
+ * compiler had its own targetNamespace). */
737
+ int same_ns = subroot && (!tns || !s->target_ns ||
738
+ strcmp(tns, s->target_ns) == 0);
697
739
  if (subroot &&
698
- (!tns || !s->target_ns ||
699
- strcmp(tns, s->target_ns) == 0)) {
740
+ (strcmp(local, "import") == 0 || same_ns)) {
700
741
  s->include_depth++;
701
742
  xsd_capture_root(s, subroot);
702
743
  s->include_depth--;
@@ -719,7 +760,7 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
719
760
  (XsdElementDecl*)calloc(1, sizeof(*d));
720
761
  if (d) {
721
762
  d->name = xsd_strdup(nm);
722
- d->type = xsd_strdup(ty);
763
+ d->type = xsd_strdup(xsd_local_name(ty));
723
764
  xsd_capture_alternatives(s, (LeptrisElement)n, d);
724
765
  d->fixed = xsd_strdup(
725
766
  leptris_element_attribute((LeptrisElement)n,
@@ -789,9 +830,9 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
789
830
  XsdAttrDecl* d = (XsdAttrDecl*)calloc(1, sizeof(*d));
790
831
  if (d) {
791
832
  d->name = xsd_strdup(nm);
792
- d->type = xsd_strdup(
833
+ d->type = xsd_strdup(xsd_local_name(
793
834
  leptris_element_attribute((LeptrisElement)n,
794
- "type"));
835
+ "type")));
795
836
  d->fixed = xsd_strdup(
796
837
  leptris_element_attribute((LeptrisElement)n,
797
838
  "fixed"));
@@ -1141,8 +1182,9 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
1141
1182
  if (!dl || (strcmp(dl, "extension") != 0 &&
1142
1183
  strcmp(dl, "restriction") != 0))
1143
1184
  continue;
1144
- const char* base =
1185
+ const char* base_raw =
1145
1186
  leptris_element_attribute(dq, "base");
1187
+ const char* base = xsd_local_name(base_raw);
1146
1188
  XsdCm* base_root =
1147
1189
  (base && strncmp(base, "xs:", 3) != 0)
1148
1190
  ? xsd_find_complex(s, base)
@@ -1199,8 +1241,8 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
1199
1241
  if (!dl || (strcmp(dl, "extension") != 0 &&
1200
1242
  strcmp(dl, "restriction") != 0))
1201
1243
  continue;
1202
- const char* base =
1203
- leptris_element_attribute(dq, "base");
1244
+ const char* base = xsd_local_name(
1245
+ leptris_element_attribute(dq, "base"));
1204
1246
  if (base) {
1205
1247
  if (strncmp(base, "xs:", 3) == 0) {
1206
1248
  root->text_type = xsd_strdup(base);
@@ -1434,8 +1476,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1434
1476
  if (!d) continue;
1435
1477
  if (an) {
1436
1478
  d->name = xsd_strdup(an);
1437
- d->type = xsd_strdup(
1438
- leptris_element_attribute(ae, "type"));
1479
+ d->type = xsd_strdup(xsd_local_name(
1480
+ leptris_element_attribute(ae, "type")));
1439
1481
  if (!d->type)
1440
1482
  d->type = xsd_strdup(
1441
1483
  xsd_inline_attr_type(s, ae, an));
@@ -1444,8 +1486,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1444
1486
  d->required = (use && strcmp(use, "required") == 0);
1445
1487
  } else {
1446
1488
  /* ref to a top-level attribute declaration */
1447
- const char* ref =
1448
- leptris_element_attribute(ae, "ref");
1489
+ const char* ref = xsd_local_name(
1490
+ leptris_element_attribute(ae, "ref"));
1449
1491
  const XsdAttrDecl* src =
1450
1492
  ref ? xsd_find_top_attr(s, ref) : NULL;
1451
1493
  if (!src) {
@@ -1465,8 +1507,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1465
1507
  head = d;
1466
1508
  tail = d;
1467
1509
  } else if (strcmp(al, "attributeGroup") == 0) {
1468
- const char* ref =
1469
- leptris_element_attribute(ae, "ref");
1510
+ const char* ref = xsd_local_name(
1511
+ leptris_element_attribute(ae, "ref"));
1470
1512
  if (!ref) continue;
1471
1513
  XsdAttrGroupDef* g = s->attr_groups;
1472
1514
  for (; g; g = g->next)
@@ -208,12 +208,56 @@ static void validate_children(struct xsd_validator* v,
208
208
  }
209
209
  int ok = xsd_content_valid(s, model, xsd_target_ns(s), names, uris,
210
210
  n);
211
- free(names);
212
- free(uris);
213
- if (!ok) {
211
+ if (ok) {
212
+ free(names);
213
+ free(uris);
214
+ } else {
215
+ /* Name the culprit: libxml2 names the offending child once
216
+ * and recovers; the unnamed form cascaded ambiguously
217
+ * (#1626 — 353 errors for one misordered pair). Greedy
218
+ * linear match against the sequence's particle list;
219
+ * non-sequence models keep the generic message. */
220
+ const char* culprit = NULL;
221
+ if (model->kind == XSD_CM_SEQ) {
222
+ XsdCm* parts[256];
223
+ size_t j = 0;
224
+ for (XsdCm* q = model->first_child; q && j < 256;
225
+ q = q->next)
226
+ parts[j++] = q;
227
+ size_t pj = 0;
228
+ for (size_t k2 = 0; k2 < n && !culprit; k2++) {
229
+ const char* uri = uris ? uris[k2] : NULL;
230
+ int matched = 0;
231
+ while (pj < j) {
232
+ XsdCm* q = parts[pj];
233
+ if (q->name && strcmp(q->name, names[k2]) == 0 &&
234
+ (!q->ns ||
235
+ (uri && strcmp(uri, q->ns) == 0))) {
236
+ matched = 1;
237
+ pj++;
238
+ break;
239
+ }
240
+ if (q->min == 0) {
241
+ pj++; /* skippable optional: not expected here */
242
+ continue;
243
+ }
244
+ culprit = names[k2]; /* expected q, found this */
245
+ break;
246
+ }
247
+ if (!matched && !culprit) culprit = names[k2];
248
+ }
249
+ }
214
250
  const char* local = NULL;
215
251
  leptris_element_expanded_name(elem, &local, NULL, NULL);
216
- verrf(v, "element", local, "children do not match the content model");
252
+ char msg[256];
253
+ snprintf(msg, sizeof(msg),
254
+ culprit
255
+ ? "children do not match the content model (at '%s')"
256
+ : "children do not match the content model",
257
+ culprit ? culprit : "");
258
+ verrf(v, "element", local, msg);
259
+ free(names);
260
+ free(uris);
217
261
  }
218
262
  /* recurse: a global declaration governs the child when one
219
263
  * exists; otherwise the parent's particle declaration does
@@ -2163,15 +2163,21 @@ TEST(Plan408, ResultBuildFeedsSerialize) {
2163
2163
  leptris_plan_row_op ops[] = {
2164
2164
  {LEPTRIS_PLAN_OP_ELEMENT, 0, UINT32_MAX, NULL, NULL, 0},
2165
2165
  {LEPTRIS_PLAN_OP_ATTR, 0, UINT32_MAX, "id", "7", 1},
2166
- {LEPTRIS_PLAN_OP_SCALAR, 0, 0, NULL, "first", 5},
2166
+ {LEPTRIS_PLAN_OP_SCALAR, 0, 0, NULL, "alpha", 5},
2167
+ {LEPTRIS_PLAN_OP_SCALAR, 0, 0, NULL, "beta", 4},
2168
+ {LEPTRIS_PLAN_OP_SCALAR, 0, 0, NULL, "gamma", 5},
2167
2169
  {LEPTRIS_PLAN_OP_END, 0, UINT32_MAX, NULL, NULL, 0},
2168
2170
  };
2169
2171
  LeptrisPlanResult r =
2170
- leptris_plan_result_build(plan, ops, 4, &st);
2172
+ leptris_plan_result_build(plan, ops, 6, &st);
2171
2173
  ASSERT_NE(r, nullptr);
2172
2174
  char* out = leptris_plan_serialize(plan, r, &st);
2173
2175
  ASSERT_NE(out, nullptr);
2174
- EXPECT_STREQ(out, "<b id=\"7\"><p>first</p></b>");
2176
+ /* #1625: multi-member order is the OP order on every platform
2177
+ * (position ties fell to unstable qsort and reversed members
2178
+ * on Windows) */
2179
+ EXPECT_STREQ(out,
2180
+ "<b id=\"7\"><p>alpha</p><p>beta</p><p>gamma</p></b>");
2175
2181
  leptris_free_string(out);
2176
2182
  leptris_plan_result_free(r);
2177
2183
  leptris_plan_free(plan);
@@ -233,6 +233,36 @@ TEST(ParserConfigurableDepth, LowerCapRejectsShallowerNesting) {
233
233
  leptris_set_max_depth(0);
234
234
  }
235
235
 
236
+ /* leptris#1577: the open-tag guard honors the configured max depth
237
+ * while the parser's depth stacks were fixed at DP_MAX_DEPTH (256) —
238
+ * any document deeper than 256 under a raised cap wrote past the
239
+ * stacks (layout-dependent state corruption). The stacks now size to
240
+ * the cap; a 600-deep document must parse and walk cleanly. */
241
+ TEST(ParserConfigurableDepth, DeepDocumentUnderRaisedCap) {
242
+ leptris_set_max_depth(1024);
243
+ std::string xml;
244
+ for (int i = 0; i < 600; i++) xml += "<d>";
245
+ xml += 'x';
246
+ for (int i = 0; i < 600; i++) xml += "</d>";
247
+
248
+ LeptrisStatus st;
249
+ LeptrisDocument doc = leptris_parse_string(xml.data(), xml.size(), &st);
250
+ ASSERT_NE(doc, nullptr);
251
+ LeptrisElement root = leptris_document_root(doc);
252
+ ASSERT_NE(root, nullptr);
253
+
254
+ /* Walk to the spine bottom: 600 opens must survive intact. */
255
+ int depth_seen = 0;
256
+ LeptrisElement e = root;
257
+ while (e) {
258
+ depth_seen++;
259
+ e = leptris_element_first_child_any(e);
260
+ }
261
+ EXPECT_EQ(depth_seen, 600); /* the spine: 600 nested <d> */
262
+ leptris_document_free(doc);
263
+ leptris_set_max_depth(0);
264
+ }
265
+
236
266
  TEST(ParserConfigurableDepth, GetReturnsEffectiveValue) {
237
267
  leptris_set_max_depth(0);
238
268
  EXPECT_EQ(leptris_get_max_depth(), 256);
@@ -1499,3 +1499,128 @@ TEST(XsdValidate, UndeclaredAttributeIsStrictUnlessAnyAttribute) {
1499
1499
  }
1500
1500
 
1501
1501
  } // namespace
1502
+
1503
+ // ---- #1626: cross-ns imports, prefixed refs, error cascade -----
1504
+ namespace {
1505
+
1506
+ /* 1. cross-namespace xs:import registers its declarations when the
1507
+ * compiling schema carries its own targetNamespace (the wml-2010
1508
+ * shape). Chameleon/same-ns worked; foreign imports were skipped. */
1509
+ TEST(XsdImport1626, CrossNamespaceImportRegisters) {
1510
+ std::string dir = ::testing::TempDir();
1511
+ FILE* f1 = fopen((dir + "x6shared.xsd").c_str(), "wb");
1512
+ ASSERT_NE(f1, nullptr);
1513
+ const char* shared =
1514
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1515
+ " xmlns:w=\"urn:shared\""
1516
+ " targetNamespace=\"urn:shared\">"
1517
+ "<xs:complexType name=\"CT_Border\">"
1518
+ "<xs:attribute name=\"val\" type=\"xs:token\"/>"
1519
+ "</xs:complexType>"
1520
+ "</xs:schema>";
1521
+ fwrite(shared, 1, strlen(shared), f1);
1522
+ fclose(f1);
1523
+ FILE* f2 = fopen((dir + "x6main.xsd").c_str(), "wb");
1524
+ ASSERT_NE(f2, nullptr);
1525
+ /* the importing schema has ITS OWN target namespace and
1526
+ * references the imported type through the shared prefix */
1527
+ const char* mainx =
1528
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1529
+ " xmlns:w=\"urn:shared\""
1530
+ " targetNamespace=\"urn:main\">"
1531
+ "<xs:import namespace=\"urn:shared\""
1532
+ " schemaLocation=\"x6shared.xsd\"/>"
1533
+ "<xs:element name=\"b\">"
1534
+ "<xs:complexType>"
1535
+ "<xs:attribute name=\"id\" type=\"xs:integer\"/>"
1536
+ "<xs:attribute name=\"bd\" type=\"w:CT_Border\"/>"
1537
+ "</xs:complexType>"
1538
+ "</xs:element>"
1539
+ "</xs:schema>";
1540
+ fwrite(mainx, 1, strlen(mainx), f2);
1541
+ fclose(f2);
1542
+
1543
+ LeptrisStatus st = LEPTRIS_OK;
1544
+ LeptrisXsdSchema s =
1545
+ leptris_xsd_compile_file((dir + "x6main.xsd").c_str(), &st);
1546
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
1547
+ /* the imported type resolves: the attribute types */
1548
+ LeptrisDocument d = leptris_parse_string(
1549
+ "<b id=\"1\" bd=\"x\"/>", strlen("<b id=\"1\" bd=\"x\"/>"), &st);
1550
+ ASSERT_NE(d, nullptr);
1551
+ EXPECT_EQ(leptris_xsd_validate(s, d), 1);
1552
+ leptris_document_free(d);
1553
+ leptris_xsd_free(s);
1554
+ remove((dir + "x6shared.xsd").c_str());
1555
+ remove((dir + "x6main.xsd").c_str());
1556
+ }
1557
+
1558
+ /* 3. prefixed type refs resolve by local name (uniword's
1559
+ * default-xmlns spelling always worked; the w: spelling did not) */
1560
+ TEST(XsdImport1626, PrefixedTypeRefsResolve) {
1561
+ const char* x =
1562
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1563
+ " xmlns:w=\"urn:x\" targetNamespace=\"urn:x\">"
1564
+ "<xs:complexType name=\"CT_Border\">"
1565
+ "<xs:attribute name=\"v\" type=\"xs:integer\"/>"
1566
+ "</xs:complexType>"
1567
+ "<xs:complexType name=\"CT_Wrap\">"
1568
+ "<xs:sequence>"
1569
+ "<xs:element name=\"b\" type=\"w:CT_Border\"/>"
1570
+ "</xs:sequence>"
1571
+ "</xs:complexType>"
1572
+ "<xs:element name=\"r\" type=\"w:CT_Wrap\"/>"
1573
+ "</xs:schema>";
1574
+ EXPECT_EQ(validate_doc(x, "<r><b v=\"1\"/></r>"), 1);
1575
+ EXPECT_EQ(validate_doc(x, "<r><b v=\"z\"/></r>"), 0);
1576
+ }
1577
+
1578
+ /* 2. error amplification: one misordered child reports ONCE, with
1579
+ * the offending child named — not once per ancestor (~175x) */
1580
+ TEST(XsdImport1626, MisorderReportsOnceNamed) {
1581
+ const char* x =
1582
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
1583
+ "<xs:complexType name=\"CT_TblBorders\">"
1584
+ "<xs:sequence>"
1585
+ "<xs:element name=\"top\" type=\"xs:token\"/>"
1586
+ "<xs:element name=\"right\" type=\"xs:token\"/>"
1587
+ "<xs:element name=\"insideH\" type=\"xs:token\"/>"
1588
+ "</xs:sequence>"
1589
+ "</xs:complexType>"
1590
+ "<xs:complexType name=\"CT_TblPr\">"
1591
+ "<xs:sequence>"
1592
+ "<xs:element name=\"borders\" type=\"CT_TblBorders\"/>"
1593
+ "<xs:element name=\"width\" type=\"xs:integer\"/>"
1594
+ "</xs:sequence>"
1595
+ "</xs:complexType>"
1596
+ "<xs:complexType name=\"CT_Tbl\">"
1597
+ "<xs:sequence>"
1598
+ "<xs:element name=\"tblPr\" type=\"CT_TblPr\"/>"
1599
+ "<xs:element name=\"row\" type=\"xs:token\"/>"
1600
+ "</xs:sequence>"
1601
+ "</xs:complexType>"
1602
+ "<xs:element name=\"tbl\" type=\"CT_Tbl\"/>"
1603
+ "</xs:schema>";
1604
+ /* insideH before right: the classic wml minimal case */
1605
+ LeptrisStatus st = LEPTRIS_OK;
1606
+ LeptrisXsdSchema s = leptris_xsd_compile(x, strlen(x), &st);
1607
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
1608
+ const char* bad =
1609
+ "<tbl><tblPr><borders><top/><insideH/><right/>"
1610
+ "</borders><width>5</width></tblPr><row/></tbl>";
1611
+ LeptrisDocument d = leptris_parse_string(bad, strlen(bad), &st);
1612
+ ASSERT_NE(d, nullptr);
1613
+ EXPECT_EQ(leptris_xsd_validate(s, d), 0);
1614
+ size_t n = leptris_xsd_error_count(s);
1615
+ EXPECT_LE(n, 3u) << "amplified: " << n << " errors";
1616
+ if (n > 0) {
1617
+ const char* e = leptris_xsd_error_at(s, 0);
1618
+ EXPECT_NE(e, nullptr);
1619
+ EXPECT_NE(strstr(e, "insideH"), nullptr)
1620
+ << "first error names the offending child: " << e;
1621
+ }
1622
+ leptris_document_free(d);
1623
+ leptris_xsd_free(s);
1624
+ }
1625
+
1626
+ } // 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.334",
4
+ "version": "1.9.336",
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.334.1
4
+ version: 1.9.336.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.