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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +149 -3
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +20 -3
- data/lib/leptris/xml/ffi.rb +7 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +113 -4
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/benchmarks/dom/bench_leptris.c +14 -6
- data/vendor-src/libleptris/src/include/leptris.h +27 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +20 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +5 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +7 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +38 -4
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +5 -1
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +86 -7
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +237 -24
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +16 -1
- data/vendor-src/libleptris/test/dom/test_dom.cpp +80 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/count.xml +3117 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/empty.xml +502 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/exists.xml +537 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/head.xml +90 -0
- data/vendor-src/libleptris/test/xquery/qt3/fn/tail.xml +68 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +124 -93
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +28 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- 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' &&
|
|
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';
|
|
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' &&
|
|
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
|
-
|
|
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
|
-
|
|
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] == '
|
|
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
|
|
906
|
+
if (arg_count < 2 || arg_count > 3) {
|
|
838
907
|
snprintf(context->error_msg, sizeof(context->error_msg),
|
|
839
|
-
"deep-equal() requires
|
|
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,
|
|
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 =
|
|
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 =
|
|
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:
|
|
244
|
-
* (
|
|
245
|
-
*
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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*
|
|
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,
|
|
5161
|
-
xpath_function_registry_register(registry, "distinct-values", fn_distinct_values, 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",
|
|
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",
|
|
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",
|
|
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;
|