leptris 1.9.232.1-arm-linux → 1.9.237.1-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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Rakefile +1 -1
  4. data/ext/leptris/native/native.c +149 -3
  5. data/lib/leptris/version.rb +1 -1
  6. data/lib/leptris/xml/document.rb +20 -3
  7. data/lib/leptris/xml/ffi.rb +7 -0
  8. data/lib/leptris/xml/native.so +0 -0
  9. data/lib/libleptris.so +0 -0
  10. data/vendor-src/README.md +1 -1
  11. data/vendor-src/libleptris/CHANGELOG.md +113 -4
  12. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  13. data/vendor-src/libleptris/benchmarks/dom/bench_leptris.c +14 -6
  14. data/vendor-src/libleptris/src/include/leptris.h +27 -0
  15. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +20 -0
  16. data/vendor-src/libleptris/src/leptris/leptris_internal.h +5 -0
  17. data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +7 -0
  18. data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +38 -4
  19. data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +5 -1
  20. data/vendor-src/libleptris/src/leptris/xpath/functions.c +86 -7
  21. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +237 -24
  22. data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +16 -1
  23. data/vendor-src/libleptris/test/dom/test_dom.cpp +80 -0
  24. data/vendor-src/libleptris/test/xquery/qt3/fn/count.xml +3117 -0
  25. data/vendor-src/libleptris/test/xquery/qt3/fn/empty.xml +502 -0
  26. data/vendor-src/libleptris/test/xquery/qt3/fn/exists.xml +537 -0
  27. data/vendor-src/libleptris/test/xquery/qt3/fn/head.xml +90 -0
  28. data/vendor-src/libleptris/test/xquery/qt3/fn/tail.xml +68 -0
  29. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +124 -93
  30. data/vendor-src/libleptris/test/xquery/test_xquery.cpp +28 -0
  31. data/vendor-src/libleptris/vcpkg.json +1 -1
  32. metadata +6 -1
@@ -332,7 +332,11 @@ int xpath_result_matches_type(struct leptris_xpath_result* v,
332
332
  const char* mc =
333
333
  (tag == (int)LEPTRIS_NODE_TEXT && n)
334
334
  ? ((XPathTextNode*)n)->content : NULL;
335
- int is_num_member = mc && mc[0] == '\x03' && mc[1] == 'N';
335
+ int is_num_member = mc && mc[0] == '\x03' &&
336
+ (mc[1] == 'N' ||
337
+ (mc[1] == 'F' &&
338
+ !((mc[2] == 'N' && mc[3] == '\x02') ||
339
+ mc[2] == 'R')));
336
340
  if (strcmp(base, "item()") == 0) {
337
341
  /* every member is an item */
338
342
  } else if (strcmp(base, "node()") == 0) {
@@ -2131,7 +2135,11 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
2131
2135
  (tag == (int)LEPTRIS_NODE_TEXT && n)
2132
2136
  ? ((XPathTextNode*)n)->content : NULL;
2133
2137
  int is_num_member = mc && mc[0] == '\x03' &&
2134
- mc[1] == 'N';
2138
+ (mc[1] == 'N' ||
2139
+ (mc[1] == 'F' &&
2140
+ !((mc[2] == 'N' &&
2141
+ mc[3] == '\x02') ||
2142
+ mc[2] == 'R')));
2135
2143
  if (strcmp(base, "item()") == 0) {
2136
2144
  /* every member is an item */
2137
2145
  } else if (strcmp(base, "node()") == 0) {
@@ -2281,17 +2289,43 @@ struct leptris_xpath_result* evaluate_operator(XPathContext* ctx,
2281
2289
  if (item->type == XPATH_RESULT_NUMBER) {
2282
2290
  /* "\x03N" marks numeric members for per-member
2283
2291
  * type checks (instance of); get_node_text
2284
- * strips it for string consumers. */
2292
+ * strips it for string consumers. "F" carries
2293
+ * xs:float members (float32-exact values) so
2294
+ * eq-based functions apply float promotion. */
2285
2295
  size_t pl = piece ? strlen(piece) : 0;
2286
2296
  char* marked = (char*)malloc(pl + 3);
2287
2297
  if (marked) {
2288
- marked[0] = '\x03'; marked[1] = 'N';
2298
+ marked[0] = '\x03';
2299
+ marked[1] = (item->atomic_type &&
2300
+ strcmp(item->atomic_type,
2301
+ "xs:float") == 0)
2302
+ ? 'F'
2303
+ : (item->atomic_type &&
2304
+ strcmp(item->atomic_type,
2305
+ "xs:decimal") == 0)
2306
+ ? 'D' : 'N';
2289
2307
  if (pl) memcpy(marked + 2, piece, pl);
2290
2308
  marked[2 + pl] = 0;
2291
2309
  XPathTextNode* tn = synth_text(marked, pl + 2);
2292
2310
  free(marked);
2293
2311
  if (tn) xpath_nodeset_add(out, tn);
2294
2312
  }
2313
+ } else if (item->type == XPATH_RESULT_BOOLEAN) {
2314
+ /* "\x03B" marks boolean members so EBV
2315
+ * round-trips (a false boolean must stay falsy
2316
+ * after subsequence/remove round-trips). */
2317
+ const char* bp =
2318
+ item->value.boolean_value ? "true" : "false";
2319
+ char* marked = (char*)malloc(strlen(bp) + 3);
2320
+ if (marked) {
2321
+ marked[0] = '\x03';
2322
+ marked[1] = 'B';
2323
+ strcpy(marked + 2, bp);
2324
+ XPathTextNode* tn =
2325
+ synth_text(marked, strlen(bp) + 2);
2326
+ free(marked);
2327
+ if (tn) xpath_nodeset_add(out, tn);
2328
+ }
2295
2329
  } else {
2296
2330
  XPathTextNode* tn =
2297
2331
  synth_text(piece ? piece : "",
@@ -231,7 +231,11 @@ char* get_node_text(void* node) {
231
231
  * per-member type checks (instance of). \x03 is invalid
232
232
  * in XML 1.0 text, so it can never collide with real
233
233
  * content — strip it for every string consumer. */
234
- if (c[0] == '\x03' && c[1] == 'N') c += 2;
234
+ if (c[0] == '\x03' &&
235
+ (c[1] == 'N' || c[1] == 'B' || c[1] == 'D' ||
236
+ (c[1] == 'F' &&
237
+ !((c[2] == 'N' && c[3] == '\x02') || c[2] == 'R'))))
238
+ c += 2;
235
239
  return leptris_strdup(c);
236
240
  }
237
241
 
@@ -732,14 +732,35 @@ static int de_node_equal(LeptrisNodeRef a, LeptrisNodeRef b) {
732
732
  default:
733
733
  return 0;
734
734
  }
735
- /* Element children: pairwise, in order. */
735
+ /* Element children: pairwise, in order. F&O 3.0: comment and
736
+ * processing-instruction children are not significant to
737
+ * deep-equality (cbcl-deep-equal-001: PI vs comment under the
738
+ * same element compares equal). */
736
739
  LeptrisNodeRef ca = leptris_node_first_child(a);
737
740
  LeptrisNodeRef cb = leptris_node_first_child(b);
738
741
  while (ca && cb) {
742
+ int tca = leptris_node_get_type(ca);
743
+ int tcb = leptris_node_get_type(cb);
744
+ if (tca == LEPTRIS_NODE_TYPE_COMMENT ||
745
+ tca == LEPTRIS_NODE_TYPE_PI) {
746
+ ca = leptris_node_next_sibling(ca);
747
+ continue;
748
+ }
749
+ if (tcb == LEPTRIS_NODE_TYPE_COMMENT ||
750
+ tcb == LEPTRIS_NODE_TYPE_PI) {
751
+ cb = leptris_node_next_sibling(cb);
752
+ continue;
753
+ }
739
754
  if (!de_node_equal(ca, cb)) return 0;
740
755
  ca = leptris_node_next_sibling(ca);
741
756
  cb = leptris_node_next_sibling(cb);
742
757
  }
758
+ while (ca && (leptris_node_get_type(ca) == LEPTRIS_NODE_TYPE_COMMENT ||
759
+ leptris_node_get_type(ca) == LEPTRIS_NODE_TYPE_PI))
760
+ ca = leptris_node_next_sibling(ca);
761
+ while (cb && (leptris_node_get_type(cb) == LEPTRIS_NODE_TYPE_COMMENT ||
762
+ leptris_node_get_type(cb) == LEPTRIS_NODE_TYPE_PI))
763
+ cb = leptris_node_next_sibling(cb);
743
764
  return ca == NULL && cb == NULL;
744
765
  }
745
766
 
@@ -752,8 +773,29 @@ typedef struct {
752
773
  char* str; /* owned for synthetic items */
753
774
  const char* borrow; /* document-lifetime text */
754
775
  LeptrisNodeRef node;
776
+ /* Typed-atom tag from the result (interned literal, not owned);
777
+ * NULL for the untyped/string class. */
778
+ const char* type;
755
779
  } DeItem;
756
780
 
781
+ /* Numeric promotion rank: xs:decimal(1) < xs:float(2) <= the
782
+ * double carrier (untyped numerics, xs:double, integers — integers
783
+ * are int64-exact in the double carrier for eq purposes). */
784
+ static int de_num_rank(const char* t) {
785
+ if (!t) return 3;
786
+ if (strcmp(t, "xs:float") == 0) return 2;
787
+ if (strcmp(t, "xs:decimal") == 0) return 1;
788
+ return 3;
789
+ }
790
+
791
+ /* The untyped/string equivalence class: plain ctor/lexical strings,
792
+ * xs:string, xs:anyURI, xs:untypedAtomic all compare by codepoint. */
793
+ static int de_str_class(const char* t) {
794
+ return !t || strcmp(t, "xs:string") == 0 ||
795
+ strcmp(t, "xs:anyURI") == 0 ||
796
+ strcmp(t, "xs:untypedAtomic") == 0;
797
+ }
798
+
757
799
  static void de_item_clear(DeItem* it) {
758
800
  if (it->str) LEPTRIS_FREE(it->str);
759
801
  it->str = NULL;
@@ -767,12 +809,28 @@ static int de_item_equal(DeItem* a, DeItem* b) {
767
809
  /* deep-equal is atomic-eq based: NaN compares equal to
768
810
  * NaN (F&O 17.4.1 — unlike the eq operator). */
769
811
  if (isnan(a->num) && isnan(b->num)) return 1;
770
- return a->num == b->num;
812
+ /* Numeric promotion by rank: the lower side converts to
813
+ * the higher (decimal->float rounds; float->double is
814
+ * exact). xs:float rounds at construction, so the plain
815
+ * compare against a double is the exact widening
816
+ * (float(1.01) ne double(1.01)) while decimal vs float
817
+ * rounds the decimal (mix-args-017 true). */
818
+ int ra = de_num_rank(a->type);
819
+ int rb = de_num_rank(b->type);
820
+ double xa = a->num, xb = b->num;
821
+ if (rb == 2 && ra < 2) xa = (double)(float)xa;
822
+ else if (ra == 2 && rb < 2) xb = (double)(float)xb;
823
+ return xa == xb;
771
824
  }
772
825
  case 2: {
773
826
  const char* as = a->str ? a->str : (a->borrow ? a->borrow : "");
774
827
  const char* bs = b->str ? b->str : (b->borrow ? b->borrow : "");
775
- return strcmp(as, bs) == 0;
828
+ if (de_str_class(a->type) && de_str_class(b->type))
829
+ return strcmp(as, bs) == 0;
830
+ if (!de_str_class(a->type) && !de_str_class(b->type) &&
831
+ strcmp(a->type, b->type) == 0)
832
+ return strcmp(as, bs) == 0;
833
+ return 0; /* typed (xs:date...) vs anything else */
776
834
  }
777
835
  default: return a->num == b->num; /* booleans as 0/1 */
778
836
  }
@@ -788,6 +846,7 @@ static size_t de_collect(struct leptris_xpath_result* r, DeItem* out,
788
846
  out[i].str = NULL;
789
847
  out[i].borrow = NULL;
790
848
  out[i].node = NULL;
849
+ out[i].type = NULL;
791
850
  int ty = XPATH_NODE_TYPE(ns->nodes[i]);
792
851
  if (ty == LEPTRIS_NODE_TEXT) {
793
852
  /* Synthetic sequence member (tag 8): content rides
@@ -795,8 +854,17 @@ static size_t de_collect(struct leptris_xpath_result* r, DeItem* out,
795
854
  * the "\x03N" marker. */
796
855
  const char* c = ((XPathTextNode*)ns->nodes[i])->content;
797
856
  c = c ? c : "";
798
- if (c[0] == '\x03' && c[1] == 'N') {
857
+ if (c[0] == '\x03' && c[1] == 'B') {
858
+ out[i].kind = 3;
859
+ out[i].num = c[2] == 't' ? 1 : 0;
860
+ } else if (c[0] == '\x03' &&
861
+ (c[1] == 'N' || c[1] == 'D' ||
862
+ (c[1] == 'F' &&
863
+ !((c[2] == 'N' && c[3] == '\x02') ||
864
+ c[2] == 'R')))) {
799
865
  out[i].kind = 1;
866
+ out[i].type = c[1] == 'F' ? "xs:float"
867
+ : c[1] == 'D' ? "xs:decimal" : NULL;
800
868
  out[i].num = strtod(c + 2, NULL);
801
869
  } else {
802
870
  out[i].kind = 2;
@@ -816,6 +884,7 @@ static size_t de_collect(struct leptris_xpath_result* r, DeItem* out,
816
884
  out[0].str = NULL;
817
885
  out[0].borrow = NULL;
818
886
  out[0].node = NULL;
887
+ out[0].type = r->atomic_type;
819
888
  if (r->type == XPATH_RESULT_NUMBER) {
820
889
  out[0].kind = 1;
821
890
  out[0].num = r->value.number_value;
@@ -834,11 +903,21 @@ static struct leptris_xpath_result* xpath_func_deep_equal(XPathContext* context,
834
903
  XPathASTNode** args,
835
904
  size_t arg_count
836
905
  ) {
837
- if (arg_count != 2) {
906
+ if (arg_count < 2 || arg_count > 3) {
838
907
  snprintf(context->error_msg, sizeof(context->error_msg),
839
- "deep-equal() requires exactly 2 arguments, got %zu", arg_count);
908
+ "deep-equal() requires 2 or 3 arguments, got %zu", arg_count);
840
909
  return NULL;
841
910
  }
911
+ if (arg_count == 3) {
912
+ /* Collation argument: the default and codepoint URIs (and,
913
+ * per the QT3 corpus, any URI here) fold to the codepoint
914
+ * comparison this engine implements; evaluation errors
915
+ * propagate. */
916
+ struct leptris_xpath_result* c =
917
+ xpath_evaluate(context, args[2]);
918
+ if (!c) return NULL;
919
+ leptris_xpath_result_free(c);
920
+ }
842
921
 
843
922
  struct leptris_xpath_result* a = xpath_evaluate(context, args[0]);
844
923
  if (!a) return NULL;
@@ -2729,7 +2808,7 @@ void xpath_function_registry_init_standard(XPathFunctionRegistry* registry) {
2729
2808
  xpath_function_registry_register(registry, "concat", xpath_func_concat, 2, -1);
2730
2809
  xpath_function_registry_register(registry, "starts-with", xpath_func_starts_with, 2, 3);
2731
2810
  xpath_function_registry_register(registry, "ends-with", xpath_func_ends_with, 2, 3);
2732
- xpath_function_registry_register(registry, "deep-equal", xpath_func_deep_equal, 2, 2);
2811
+ xpath_function_registry_register(registry, "deep-equal", xpath_func_deep_equal, 2, 3);
2733
2812
  xpath_function_registry_register(registry, "contains", xpath_func_contains, 2, 3);
2734
2813
  xpath_function_registry_register(registry, "substring", xpath_func_substring, 2, 3);
2735
2814
  xpath_function_registry_register(registry, "substring-before", xpath_func_substring_before, 2, 3);
@@ -133,6 +133,99 @@ static void free_items(char** items, size_t n) {
133
133
  free(items);
134
134
  }
135
135
 
136
+ /* Marker-preserving collect: synthetic members keep their raw
137
+ * content so the "N" numeric marker survives the round-trip
138
+ * (get_node_text strips it). deep-equal's kind split then agrees
139
+ * on both sides of reverse()/etc., and eq-based functions can
140
+ * apply numeric semantics (NaN never matches, 2.0 == 2). */
141
+ static char** collect_items_raw(XPathContext* ctx, XPathASTNode** args,
142
+ size_t n, size_t i, size_t* out_n) {
143
+ *out_n = 0;
144
+ struct leptris_xpath_result* r = xpath_evaluate(ctx, args[i]);
145
+ if (!r) return NULL;
146
+ char** items = NULL;
147
+ size_t cnt = 0;
148
+ if (r->type == XPATH_RESULT_NODESET && r->value.nodeset_value) {
149
+ XPathNodeSet* ns = r->value.nodeset_value;
150
+ items = (char**)malloc((ns->count ? ns->count : 1) * sizeof(char*));
151
+ if (!items) { xpath_result_free(r); return NULL; }
152
+ for (size_t k = 0; k < ns->count; k++) {
153
+ void* nd = ns->nodes[k];
154
+ if (XPATH_NODE_TYPE(nd) == LEPTRIS_NODE_TEXT) {
155
+ const char* c = ((XPathTextNode*)nd)->content;
156
+ items[cnt++] = leptris_strdup(c ? c : "");
157
+ } else {
158
+ char* t = get_node_text(nd);
159
+ items[cnt++] = t ? t : leptris_strdup("");
160
+ }
161
+ }
162
+ } else if (r->type == XPATH_RESULT_NUMBER) {
163
+ char* s = scalar_str(r);
164
+ size_t sl = s ? strlen(s) : 0;
165
+ items = (char**)malloc(sizeof(char*));
166
+ if (!items) { free(s); xpath_result_free(r); return NULL; }
167
+ char* m = (char*)malloc(sl + 3);
168
+ if (m) {
169
+ m[0] = '\x03';
170
+ m[1] = (r->atomic_type &&
171
+ strcmp(r->atomic_type, "xs:float") == 0)
172
+ ? 'F'
173
+ : (r->atomic_type &&
174
+ strcmp(r->atomic_type, "xs:decimal") == 0)
175
+ ? 'D' : 'N';
176
+ if (sl) memcpy(m + 2, s, sl);
177
+ m[2 + sl] = 0;
178
+ }
179
+ items[cnt++] = m ? m : leptris_strdup("");
180
+ free(s);
181
+ } else {
182
+ items = (char**)malloc(sizeof(char*));
183
+ if (!items) { xpath_result_free(r); return NULL; }
184
+ items[cnt++] = scalar_str(r);
185
+ }
186
+ xpath_result_free(r);
187
+ *out_n = cnt;
188
+ (void)n;
189
+ return items;
190
+ }
191
+
192
+ /* eq-based atomic comparison over raw item strings: both-marked
193
+ * compares numerically; both-plain compares by codepoint; number
194
+ * vs string is false. nan_equal: distinct-values dedups NaN with
195
+ * itself (F&O 3.1), fn:index-of's eq never matches it. 'F' markers
196
+ * carry xs:float members (float32-exact values); an F vs plain-N
197
+ * pair promotes the plain side DOWN to float (decimal-vs-float
198
+ * promotes decimal to float per F&O numeric promotion). */
199
+ static int atom_seq_eq_n(const char* a, const char* b, int nan_equal) {
200
+ int ka = (a[0] == '\x03' && a[1] == 'N') ? 'N'
201
+ : (a[0] == '\x03' && a[1] == 'F') ? 'F'
202
+ : (a[0] == '\x03' && a[1] == 'D') ? 'D'
203
+ : (a[0] == '\x03' && a[1] == 'B') ? 'B' : 0;
204
+ int kb = (b[0] == '\x03' && b[1] == 'N') ? 'N'
205
+ : (b[0] == '\x03' && b[1] == 'F') ? 'F'
206
+ : (b[0] == '\x03' && b[1] == 'D') ? 'D'
207
+ : (b[0] == '\x03' && b[1] == 'B') ? 'B' : 0;
208
+ if (!ka != !kb) return 0;
209
+ if (ka == 'B' && kb == 'B') return strcmp(a + 2, b + 2) == 0;
210
+ if (!ka) return strcmp(a, b) == 0;
211
+ /* xs:decimal eq is exact — the double carrier cannot hold 30
212
+ * significant digits, so same-type decimals compare lexically
213
+ * (equal-value canonical decimals share their spelling). */
214
+ if (ka == 'D' && kb == 'D') return strcmp(a + 2, b + 2) == 0;
215
+ double da = strtod(a + 2, NULL);
216
+ double db = strtod(b + 2, NULL);
217
+ int na = isnan(da), nb = isnan(db);
218
+ /* NaN eq nothing (distinct-values dedups NaN with itself) */
219
+ if (na || nb) return na && nb ? nan_equal : 0;
220
+ if (ka == 'F' && kb != 'F') db = (double)(float)db;
221
+ else if (kb == 'F' && ka != 'F') da = (double)(float)da;
222
+ return da == db;
223
+ }
224
+
225
+ static int atom_seq_eq(const char* a, const char* b) {
226
+ return atom_seq_eq_n(a, b, 0);
227
+ }
228
+
136
229
  /* Positional helpers (1-based; start may be <= 0, len NaN = to end). */
137
230
  static void seq_from_items(struct leptris_xpath_result* seq, char** items,
138
231
  size_t n, long start, long len) {
@@ -192,7 +285,7 @@ static struct leptris_xpath_result* fn_tail(XPathContext* ctx,
192
285
  static struct leptris_xpath_result* fn_reverse(XPathContext* ctx,
193
286
  XPathASTNode** args, size_t n) {
194
287
  size_t cnt;
195
- char** items = collect_items(ctx, args, n, 0, &cnt);
288
+ char** items = collect_items_raw(ctx, args, n, 0, &cnt);
196
289
  if (!items) return NULL;
197
290
  struct leptris_xpath_result* out = seq_new();
198
291
  if (out)
@@ -227,7 +320,7 @@ static struct leptris_xpath_result* fn_unordered(XPathContext* ctx,
227
320
  static struct leptris_xpath_result* fn_subsequence(XPathContext* ctx,
228
321
  XPathASTNode** args, size_t n) {
229
322
  size_t cnt;
230
- char** items = collect_items(ctx, args, n, 0, &cnt);
323
+ char** items = collect_items_raw(ctx, args, n, 0, &cnt);
231
324
  if (!items) return NULL;
232
325
  struct leptris_xpath_result* sv = xpath_evaluate(ctx, args[1]);
233
326
  double sd = sv ? leptris_xpath_result_number(sv) : 0;
@@ -240,16 +333,40 @@ static struct leptris_xpath_result* fn_subsequence(XPathContext* ctx,
240
333
  if (sv) leptris_xpath_result_free(sv);
241
334
  struct leptris_xpath_result* out = seq_new();
242
335
  if (out) {
243
- /* F&O: a negative $length yields the empty sequence
244
- * (K-SeqSubsequenceFunc-5) — len < 0 is NOT the "to end"
245
- * sentinel here. */
246
- if (n >= 3 && ld < 0) {
247
- free_items(items, cnt);
248
- return out;
336
+ /* F&O window in position space: item p (1-based) is in the
337
+ * result iff round(start) <= p and, 3-arg form, also
338
+ * p < round(start) + round(len). NaN compares false ->
339
+ * empty (cbcl-subsequence-002/003/005). Positions are NOT
340
+ * clamped to 1 — a negative start with a short length
341
+ * covers no positive position (cbcl-subsequence-009).
342
+ * start/len round half toward +infinity like fn:round
343
+ * (K2-SeqSubsequenceFunc-5: 1.8 -> 2). */
344
+ double start = (sd != sd) ? 1.0 : floor(sd + 0.5);
345
+ double len = 0;
346
+ int has_len = 0;
347
+ if (n >= 3) {
348
+ /* NaN length or negative length: empty */
349
+ if (ld != ld || ld < 0) {
350
+ free_items(items, cnt);
351
+ return out;
352
+ }
353
+ len = floor(ld + 0.5);
354
+ has_len = 1;
355
+ }
356
+ for (size_t k = 0; k < cnt; k++) {
357
+ double p = (double)(k + 1);
358
+ /* The spec's own window: p >= round(start) and, 3-arg
359
+ * form, p < round(start) + round(len). The sum can be
360
+ * NaN (-INF + INF) making every comparison false ->
361
+ * empty (cbcl-subsequence-001), so the membership test
362
+ * must stay NaN-safe — no early break. The 2-arg form
363
+ * has no upper bound (cbcl-subsequence-004: start=-INF
364
+ * selects everything; a sentinel length would collide
365
+ * with -inf + finite = -inf). */
366
+ if (!(p >= start)) continue;
367
+ if (has_len && !(p < start + len)) continue;
368
+ seq_push_str(out, items[k]);
249
369
  }
250
- long start = (long)sd;
251
- long len = (n >= 3) ? (ld != ld ? -1 : (long)ld) : -1;
252
- seq_from_items(out, items, cnt, start, len);
253
370
  }
254
371
  free_items(items, cnt);
255
372
  return out;
@@ -258,7 +375,7 @@ static struct leptris_xpath_result* fn_subsequence(XPathContext* ctx,
258
375
  static struct leptris_xpath_result* fn_remove(XPathContext* ctx,
259
376
  XPathASTNode** args, size_t n) {
260
377
  size_t cnt;
261
- char** items = collect_items(ctx, args, n, 0, &cnt);
378
+ char** items = collect_items_raw(ctx, args, n, 0, &cnt);
262
379
  if (!items) return NULL;
263
380
  struct leptris_xpath_result* iv = xpath_evaluate(ctx, args[1]);
264
381
  long drop = iv ? (long)leptris_xpath_result_number(iv) : 0;
@@ -301,14 +418,54 @@ static struct leptris_xpath_result* fn_insert_before(XPathContext* ctx,
301
418
  static struct leptris_xpath_result* fn_index_of(XPathContext* ctx,
302
419
  XPathASTNode** args, size_t n) {
303
420
  size_t cnt;
304
- char** items = collect_items(ctx, args, n, 0, &cnt);
421
+ char** items = collect_items_raw(ctx, args, n, 0, &cnt);
305
422
  if (!items) return NULL;
423
+ /* Collation argument (3-arg form): accepted, folds to the
424
+ * codepoint comparison; evaluation errors propagate. */
425
+ if (n > 2) {
426
+ struct leptris_xpath_result* c = xpath_evaluate(ctx, args[2]);
427
+ if (!c) { free_items(items, cnt); return NULL; }
428
+ leptris_xpath_result_free(c);
429
+ }
306
430
  struct leptris_xpath_result* v = xpath_evaluate(ctx, args[1]);
307
- char* needle = v ? scalar_str(v) : NULL;
431
+ char* needle = NULL;
432
+ if (v && v->type == XPATH_RESULT_BOOLEAN) {
433
+ /* B-marked members must match B-marked needles */
434
+ const char* bp = v->value.boolean_value ? "true" : "false";
435
+ needle = (char*)malloc(strlen(bp) + 3);
436
+ if (needle) {
437
+ needle[0] = '\x03';
438
+ needle[1] = 'B';
439
+ strcpy(needle + 2, bp);
440
+ } else {
441
+ needle = leptris_strdup("");
442
+ }
443
+ } else if (v && v->type == XPATH_RESULT_NUMBER) {
444
+ char* s = scalar_str(v);
445
+ size_t sl = s ? strlen(s) : 0;
446
+ needle = (char*)malloc(sl + 3);
447
+ if (needle) {
448
+ needle[0] = '\x03';
449
+ needle[1] = (v->atomic_type &&
450
+ strcmp(v->atomic_type, "xs:float") == 0)
451
+ ? 'F'
452
+ : (v->atomic_type &&
453
+ strcmp(v->atomic_type, "xs:decimal") == 0)
454
+ ? 'D' : 'N';
455
+ if (sl) memcpy(needle + 2, s, sl);
456
+ needle[2 + sl] = 0;
457
+ } else {
458
+ needle = leptris_strdup("");
459
+ }
460
+ free(s);
461
+ } else {
462
+ needle = v ? scalar_str(v) : NULL;
463
+ }
308
464
  struct leptris_xpath_result* out = seq_new();
309
465
  if (out && needle)
310
466
  for (size_t k = 0; k < cnt; k++)
311
- if (strcmp(items[k], needle) == 0) seq_push_num(out, (double)(k + 1));
467
+ if (atom_seq_eq(items[k], needle))
468
+ seq_push_num(out, (double)(k + 1));
312
469
  free(needle);
313
470
  if (v) leptris_xpath_result_free(v);
314
471
  free_items(items, cnt);
@@ -318,14 +475,23 @@ static struct leptris_xpath_result* fn_index_of(XPathContext* ctx,
318
475
  static struct leptris_xpath_result* fn_distinct_values(XPathContext* ctx,
319
476
  XPathASTNode** args, size_t n) {
320
477
  size_t cnt;
321
- char** items = collect_items(ctx, args, n, 0, &cnt);
478
+ char** items = collect_items_raw(ctx, args, n, 0, &cnt);
322
479
  if (!items) return NULL;
480
+ /* 2-arg form: collation accepted (folds to codepoint); errors
481
+ * propagate. */
482
+ if (n > 1) {
483
+ struct leptris_xpath_result* c = xpath_evaluate(ctx, args[1]);
484
+ if (!c) { free_items(items, cnt); return NULL; }
485
+ leptris_xpath_result_free(c);
486
+ }
323
487
  struct leptris_xpath_result* out = seq_new();
324
488
  if (out)
325
489
  for (size_t k = 0; k < cnt; k++) {
326
490
  int dup = 0;
327
491
  for (size_t j = 0; j < k && !dup; j++)
328
- if (strcmp(items[k], items[j]) == 0) dup = 1;
492
+ if (atom_seq_eq_n(items[k], items[j], 1)) dup = 1;
493
+ /* first occurrence keeps its original spelling
494
+ * (distinct-values((1, 2.0, 3, 2)) = 1, 2.0, 3) */
329
495
  if (!dup) seq_push_str(out, items[k]);
330
496
  }
331
497
  free_items(items, cnt);
@@ -2761,7 +2927,36 @@ static struct leptris_xpath_result* fn_xs_integer(XPathContext* ctx,
2761
2927
  }
2762
2928
  static struct leptris_xpath_result* fn_xs_double(XPathContext* ctx,
2763
2929
  XPathASTNode** a, size_t n) {
2764
- return fn_xs_number_ctor(ctx, a, n, 0);
2930
+ struct leptris_xpath_result* r = fn_xs_number_ctor(ctx, a, n, 0);
2931
+ if (r) r->atomic_type = "xs:double";
2932
+ return r;
2933
+ }
2934
+
2935
+ static struct leptris_xpath_result* fn_xs_float(XPathContext* ctx,
2936
+ XPathASTNode** a, size_t n) {
2937
+ struct leptris_xpath_result* r = fn_xs_number_ctor(ctx, a, n, 0);
2938
+ if (r) {
2939
+ /* xs:float keeps float32 precision: the value is the float
2940
+ * nearest the lexical form, widened for the double carrier
2941
+ * (deep-equal's widening compare is then exact). */
2942
+ r->value.number_value = (double)((float)r->value.number_value);
2943
+ r->atomic_type = "xs:float";
2944
+ }
2945
+ return r;
2946
+ }
2947
+
2948
+ static struct leptris_xpath_result* fn_xs_date_t(XPathContext* ctx,
2949
+ XPathASTNode** args, size_t n) {
2950
+ struct leptris_xpath_result* out = fn_passthrough_ctor(ctx, args, n);
2951
+ if (out) out->atomic_type = "xs:date";
2952
+ return out;
2953
+ }
2954
+
2955
+ static struct leptris_xpath_result* fn_xs_datetime_t(XPathContext* ctx,
2956
+ XPathASTNode** args, size_t n) {
2957
+ struct leptris_xpath_result* out = fn_passthrough_ctor(ctx, args, n);
2958
+ if (out) out->atomic_type = "xs:dateTime";
2959
+ return out;
2765
2960
  }
2766
2961
 
2767
2962
  /* ---- integer-subtype constructors (QT3 fn/concat): the same
@@ -2845,7 +3040,7 @@ XS_INT_SUB(fn_xs_nonneg, 0, LLONG_MAX, "xs:nonNegativeInteger")
2845
3040
 
2846
3041
  /* xs:decimal: an all-integer lexical keeps int64 fidelity
2847
3042
  * (QT3 fn/concatdec2args); fractions ride the double ctor. */
2848
- static struct leptris_xpath_result* fn_xs_decimal(XPathContext* ctx,
3043
+ static struct leptris_xpath_result* fn_xs_decimal_impl(XPathContext* ctx,
2849
3044
  XPathASTNode** a, size_t n) {
2850
3045
  struct leptris_xpath_result* r = xpath_evaluate(ctx, a[0]);
2851
3046
  if (!r) return NULL;
@@ -2868,6 +3063,13 @@ static struct leptris_xpath_result* fn_xs_decimal(XPathContext* ctx,
2868
3063
  return fn_xs_number_ctor(ctx, a, n, 0);
2869
3064
  }
2870
3065
 
3066
+ static struct leptris_xpath_result* fn_xs_decimal(XPathContext* ctx,
3067
+ XPathASTNode** a, size_t n) {
3068
+ struct leptris_xpath_result* r = fn_xs_decimal_impl(ctx, a, n);
3069
+ if (r) r->atomic_type = "xs:decimal";
3070
+ return r;
3071
+ }
3072
+
2871
3073
  static struct leptris_xpath_result* fn_xs_boolean(XPathContext* ctx,
2872
3074
  XPathASTNode** args, size_t n) {
2873
3075
  struct leptris_xpath_result* r = xpath_evaluate(ctx, args[0]);
@@ -5157,8 +5359,8 @@ void xpath_register_fn31(XPathFunctionRegistry* registry) {
5157
5359
  xpath_function_registry_register(registry, "subsequence", fn_subsequence, 2, 3);
5158
5360
  xpath_function_registry_register(registry, "remove", fn_remove, 2, 2);
5159
5361
  xpath_function_registry_register(registry, "insert-before", fn_insert_before, 3, 3);
5160
- xpath_function_registry_register(registry, "index-of", fn_index_of, 2, 2);
5161
- xpath_function_registry_register(registry, "distinct-values", fn_distinct_values, 1, 1);
5362
+ xpath_function_registry_register(registry, "index-of", fn_index_of, 2, 3);
5363
+ xpath_function_registry_register(registry, "distinct-values", fn_distinct_values, 1, 2);
5162
5364
  xpath_function_registry_register(registry, "avg", fn_avg, 1, 1);
5163
5365
  xpath_function_registry_register(registry, "min", fn_min, 1, 1);
5164
5366
  xpath_function_registry_register(registry, "max", fn_max, 1, 1);
@@ -5242,13 +5444,13 @@ void xpath_register_fn31(XPathFunctionRegistry* registry) {
5242
5444
  xpath_function_registry_register(registry, "namespace-uri-from-QName", fn_ns_from_qname, 1, 1);
5243
5445
  xpath_function_registry_register(registry, "node-name", fn_node_name, 1, 1);
5244
5446
  /* Dates & durations (05, first slice) — canonical xs: prefix. */
5245
- xpath_function_registry_register(registry, "xs:date", fn_passthrough_ctor, 1, 1);
5447
+ xpath_function_registry_register(registry, "xs:date", fn_xs_date_t, 1, 1);
5246
5448
  /* Atomic constructors (06) — canonical xs: prefix. */
5247
5449
  xpath_function_registry_register(registry, "xs:string", fn_passthrough_ctor, 1, 1);
5248
5450
  xpath_function_registry_register(registry, "xs:anyURI", fn_passthrough_ctor, 1, 1);
5249
5451
  xpath_function_registry_register(registry, "xs:integer", fn_xs_integer, 1, 1);
5250
5452
  xpath_function_registry_register(registry, "xs:double", fn_xs_double, 1, 1);
5251
- xpath_function_registry_register(registry, "xs:float", fn_xs_double, 1, 1);
5453
+ xpath_function_registry_register(registry, "xs:float", fn_xs_float, 1, 1);
5252
5454
  xpath_function_registry_register(registry, "xs:decimal", fn_xs_decimal, 1, 1);
5253
5455
  xpath_function_registry_register(registry, "xs:int", fn_xs_int, 1, 1);
5254
5456
  xpath_function_registry_register(registry, "xs:long", fn_xs_long, 1, 1);
@@ -5260,12 +5462,23 @@ void xpath_register_fn31(XPathFunctionRegistry* registry) {
5260
5462
  xpath_function_registry_register(registry, "xs:nonPositiveInteger", fn_xs_nonpos, 1, 1);
5261
5463
  xpath_function_registry_register(registry, "xs:nonNegativeInteger", fn_xs_nonneg, 1, 1);
5262
5464
  xpath_function_registry_register(registry, "xs:boolean", fn_xs_boolean, 1, 1);
5263
- xpath_function_registry_register(registry, "xs:dateTime", fn_passthrough_ctor, 1, 1);
5465
+ xpath_function_registry_register(registry, "xs:dateTime", fn_xs_datetime_t, 1, 1);
5264
5466
  xpath_function_registry_register(registry, "xs:time", fn_time_ctor, 1, 1);
5265
5467
  xpath_function_registry_register(registry, "xs:duration", fn_passthrough_ctor, 1, 1);
5266
5468
  xpath_function_registry_register(registry, "xs:dayTimeDuration", fn_passthrough_ctor, 1, 1);
5267
5469
  xpath_function_registry_register(registry, "xs:yearMonthDuration", fn_passthrough_ctor, 1, 1);
5268
5470
  xpath_function_registry_register(registry, "xs:QName", fn_passthrough_ctor, 1, 1);
5471
+ /* String-classed and gREG surface (QT3 deep-equal/index-of/
5472
+ * distinct-values families): untypedAtomic is string-equal;
5473
+ * binary/gREG compare by lexical form in this engine. */
5474
+ xpath_function_registry_register(registry, "xs:untypedAtomic", fn_passthrough_ctor, 1, 1);
5475
+ xpath_function_registry_register(registry, "xs:hexBinary", fn_passthrough_ctor, 1, 1);
5476
+ xpath_function_registry_register(registry, "xs:base64Binary", fn_passthrough_ctor, 1, 1);
5477
+ xpath_function_registry_register(registry, "xs:gYear", fn_passthrough_ctor, 1, 1);
5478
+ xpath_function_registry_register(registry, "xs:gYearMonth", fn_passthrough_ctor, 1, 1);
5479
+ xpath_function_registry_register(registry, "xs:gMonthDay", fn_passthrough_ctor, 1, 1);
5480
+ xpath_function_registry_register(registry, "xs:gMonth", fn_passthrough_ctor, 1, 1);
5481
+ xpath_function_registry_register(registry, "xs:gDay", fn_passthrough_ctor, 1, 1);
5269
5482
  xpath_function_registry_register(registry, "implicit-timezone", fn_implicit_tz, 0, 0);
5270
5483
  xpath_function_registry_register(registry, "current-dateTime", fn_current_dt, 0, 0);
5271
5484
  xpath_function_registry_register(registry, "current-date", fn_current_date, 0, 0);
@@ -397,7 +397,11 @@ LEPTRIS_API const char* leptris_xpath_result_node_value(
397
397
  * per-member type checks — never show it to public
398
398
  * consumers. */
399
399
  if (text->content && text->content[0] == '\x03' &&
400
- text->content[1] == 'N')
400
+ (text->content[1] == 'N' || text->content[1] == 'B' ||
401
+ text->content[1] == 'D' ||
402
+ (text->content[1] == 'F' &&
403
+ !((text->content[2] == 'N' && text->content[3] == '\x02') ||
404
+ text->content[2] == 'R'))))
401
405
  return text->content + 2;
402
406
  return text->content;
403
407
  }
@@ -415,6 +419,17 @@ LEPTRIS_API int leptris_xpath_result_boolean(LeptrisXPathResult result) {
415
419
  case XPATH_RESULT_STRING:
416
420
  return (result->value.string_value && result->value.string_value[0] != '\0') ? 1 : 0;
417
421
  case XPATH_RESULT_NODESET:
422
+ if (result->value.nodeset_value &&
423
+ result->value.nodeset_value->count == 1) {
424
+ void* nd = result->value.nodeset_value->nodes[0];
425
+ if (nd && (int)XPATH_NODE_TYPE(nd) == (int)LEPTRIS_NODE_TEXT) {
426
+ XPathTextNode* tn = (XPathTextNode*)nd;
427
+ /* "\x03B" boolean members carry their EBV */
428
+ if (tn->content && tn->content[0] == '\x03' &&
429
+ tn->content[1] == 'B')
430
+ return tn->content[2] == 't';
431
+ }
432
+ }
418
433
  return (result->value.nodeset_value && result->value.nodeset_value->count > 0) ? 1 : 0;
419
434
  default:
420
435
  return 0;