Test case for YAZ-834
[yaz-moved-to-github.git] / test / test_icu.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) Index Data
3  * See the file LICENSE for details.
4  */
5
6 /* DO NOT EDIT THIS FILE IF YOUR EDITOR DOES NOT SUPPORT UTF-8 */
7
8
9 #if HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #define USE_TIMING 0
14 #if USE_TIMING
15 #include <yaz/timing.h>
16 #endif
17
18 #include <yaz/test.h>
19 #include <yaz/log.h>
20 #include <yaz/wrbuf.h>
21
22 #if YAZ_HAVE_ICU
23 #include <yaz/icu_I18N.h>
24 #include <unicode/uclean.h>
25
26 #if YAZ_POSIX_THREADS
27 #include <pthread.h>
28 #endif
29
30 #if YAZ_HAVE_XML2
31 #include <libxml/xmlmemory.h>
32 #endif
33
34 #include <string.h>
35 #include <stdlib.h>
36
37 #define MAX_KEY_SIZE 256
38 struct icu_termmap
39 {
40     uint8_t sort_key[MAX_KEY_SIZE]; /* standard C string '\0' terminated */
41     char disp_term[MAX_KEY_SIZE];  /* standard C utf-8 string */
42 };
43
44
45 static int icu_termmap_cmp(const void *vp1, const void *vp2)
46 {
47     struct icu_termmap *itmp1 = *(struct icu_termmap **) vp1;
48     struct icu_termmap *itmp2 = *(struct icu_termmap **) vp2;
49
50     int cmp = 0;
51
52     cmp = strcmp((const char *)itmp1->sort_key,
53                  (const char *)itmp2->sort_key);
54     return cmp;
55 }
56
57
58 static int test_icu_casemap(const char *locale, char action,
59                             const char *src8cstr, const char *chk8cstr)
60 {
61     int success = 0;
62     UErrorCode status = U_ZERO_ERROR;
63
64     struct icu_buf_utf8 *src8 = icu_buf_utf8_create(0);
65     struct icu_buf_utf8 *dest8 = icu_buf_utf8_create(0);
66     struct icu_buf_utf16 *src16 = icu_buf_utf16_create(0);
67     struct icu_buf_utf16 *dest16 = icu_buf_utf16_create(0);
68
69
70     int src8cstr_len = strlen(src8cstr);
71     int chk8cstr_len = strlen(chk8cstr);
72
73     /* converting to UTF16 */
74     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
75
76     /* perform case mapping */
77     icu_utf16_casemap(dest16, src16, locale, action, &status);
78
79     /* converting to UTF8 */
80     icu_utf16_to_utf8(dest8, dest16, &status);
81
82     /* determine success */
83     if (dest8->utf8
84         && (dest8->utf8_len == strlen(chk8cstr))
85         && !strcmp(chk8cstr, (const char *) dest8->utf8))
86         success = 1;
87     else
88         success = 0;
89
90     /* report failures */
91     if (!success)
92     {
93         yaz_log(YLOG_WARN, "test_icu_casemap failed");
94         yaz_log(YLOG_LOG, "Original string:   '%s' (%d)",
95                 src8cstr, src8cstr_len);
96         yaz_log(YLOG_LOG, "icu_casemap '%s:%c' '%s' (%d)",
97                 locale, action, dest8->utf8, dest8->utf8_len);
98         yaz_log(YLOG_LOG, "expected string:   '%s' (%d)",
99                 chk8cstr, chk8cstr_len);
100     }
101
102     /* clean the buffers */
103     icu_buf_utf8_destroy(src8);
104     icu_buf_utf8_destroy(dest8);
105     icu_buf_utf16_destroy(src16);
106     icu_buf_utf16_destroy(dest16);
107
108     return success;
109 }
110
111 static void check_icu_casemap(void)
112 {
113     /* Locale 'en' */
114
115     /* successful tests */
116     YAZ_CHECK(test_icu_casemap("en", 'l',
117                                "A ReD fOx hunTS sQUirriLs",
118                                "a red fox hunts squirrils"));
119
120     YAZ_CHECK(test_icu_casemap("en", 'u',
121                                "A ReD fOx hunTS sQUirriLs",
122                                "A RED FOX HUNTS SQUIRRILS"));
123
124     YAZ_CHECK(test_icu_casemap("en", 'f',
125                                "A ReD fOx hunTS sQUirriLs",
126                                "a red fox hunts squirrils"));
127
128     YAZ_CHECK(test_icu_casemap("en", 't',
129                                "A ReD fOx hunTS sQUirriLs",
130                                "A Red Fox Hunts Squirrils"));
131
132     /* Locale 'da' */
133
134     /* success expected */
135     YAZ_CHECK(test_icu_casemap("da", 'l',
136                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN",
137                                "åh æble, øs fløde i åen efter blåbærgrøden"));
138
139     YAZ_CHECK(test_icu_casemap("da", 'u',
140                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN",
141                                "ÅH ÆBLE, ØS FLØDE I ÅEN EFTER BLÅBÆRGRØDEN"));
142
143     YAZ_CHECK(test_icu_casemap("da", 'f',
144                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN",
145                                "åh æble, øs fløde i åen efter blåbærgrøden"));
146
147     YAZ_CHECK(test_icu_casemap("da", 't',
148                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN",
149                                "Åh Æble, Øs Fløde I Åen Efter Blåbærgrøden"));
150
151     /* Locale 'de' */
152
153     /* success expected */
154     YAZ_CHECK(test_icu_casemap("de", 'l',
155                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
156                                "zwölf ärgerliche würste rollen über die straße"));
157
158     YAZ_CHECK(test_icu_casemap("de", 'u',
159                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
160                                "ZWÖLF ÄRGERLICHE WÜRSTE ROLLEN ÜBER DIE STRASSE"));
161
162     YAZ_CHECK(test_icu_casemap("de", 'f',
163                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
164                                "zwölf ärgerliche würste rollen über die strasse"));
165
166     YAZ_CHECK(test_icu_casemap("de", 't',
167                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
168                                "Zwölf Ärgerliche Würste Rollen Über Die Straße"));
169
170 }
171
172 static int test_icu_sortmap(const char *locale, int src_list_len,
173                             const char **src_list, const char **chk_list)
174 {
175     int success = 1;
176
177     UErrorCode status = U_ZERO_ERROR;
178
179     struct icu_buf_utf8 *buf8 = icu_buf_utf8_create(0);
180     struct icu_buf_utf16 *buf16 = icu_buf_utf16_create(0);
181
182     int i;
183
184     struct icu_termmap *list[src_list_len];
185
186     UCollator *coll = ucol_open(locale, &status);
187     icu_check_status(status);
188
189     if (U_FAILURE(status))
190         return 0;
191
192     /* assigning display terms and sort keys using buf 8 and buf16 */
193     for (i = 0; i < src_list_len; i++)
194     {
195
196         list[i] = (struct icu_termmap *) malloc(sizeof(struct icu_termmap));
197
198         /* copy display term */
199         strcpy(list[i]->disp_term, src_list[i]);
200
201         /* transforming to UTF16 */
202         icu_utf16_from_utf8_cstr(buf16, list[i]->disp_term, &status);
203         icu_check_status(status);
204
205         /* computing sortkeys */
206         icu_sortkey8_from_utf16(coll, buf8, buf16, &status);
207         icu_check_status(status);
208
209         /* assigning sortkeys */
210         memcpy(list[i]->sort_key, buf8->utf8, buf8->utf8_len);
211     }
212
213     /* do the sorting */
214     qsort(list, src_list_len, sizeof(struct icu_termmap *), icu_termmap_cmp);
215
216     /* checking correct sorting */
217     for (i = 0; i < src_list_len; i++)
218     {
219         if (0 != strcmp(list[i]->disp_term, chk_list[i])){
220             success = 0;
221         }
222     }
223
224     if (!success)
225     {
226         yaz_log(YLOG_LOG, "ERROR");
227         yaz_log(YLOG_LOG, "Input str:'%s':", locale);
228         for (i = 0; i < src_list_len; i++) {
229             yaz_log(YLOG_LOG, "  '%s'", list[i]->disp_term);
230         }
231         yaz_log(YLOG_LOG, "ICU sort: '%s':", locale);
232         for (i = 0; i < src_list_len; i++) {
233             yaz_log(YLOG_LOG, " '%s'", list[i]->disp_term);
234         }
235         yaz_log(YLOG_LOG, "Expected: '%s':", locale);
236         for (i = 0; i < src_list_len; i++) {
237             yaz_log(YLOG_LOG, " '%s'", chk_list[i]);
238         }
239     }
240
241     for (i = 0; i < src_list_len; i++)
242         free(list[i]);
243
244     ucol_close(coll);
245
246     icu_buf_utf8_destroy(buf8);
247     icu_buf_utf16_destroy(buf16);
248
249     return success;
250 }
251
252 static void check_icu_sortmap(void)
253 {
254     /* successful tests */
255     size_t en_1_len = 6;
256     const char *en_1_src[6] = {"z", "K", "a", "A", "Z", "k"};
257     const char *en_1_cck[6] = {"a", "A", "k", "K", "z", "Z"};
258     YAZ_CHECK(test_icu_sortmap("en", en_1_len, en_1_src, en_1_cck));
259     YAZ_CHECK(test_icu_sortmap("en_AU", en_1_len, en_1_src, en_1_cck));
260     YAZ_CHECK(test_icu_sortmap("en_CA", en_1_len, en_1_src, en_1_cck));
261     YAZ_CHECK(test_icu_sortmap("en_GB", en_1_len, en_1_src, en_1_cck));
262     YAZ_CHECK(test_icu_sortmap("en_US", en_1_len, en_1_src, en_1_cck));
263
264     /* successful tests */
265     {
266         size_t da_1_len = 6;
267         const char *da_1_src[6] = {"z", "å", "o", "æ", "a", "ø"};
268         const char *da_1_cck[6] = {"a", "o", "z", "æ", "ø", "å"};
269         YAZ_CHECK(test_icu_sortmap("da", da_1_len, da_1_src, da_1_cck));
270         YAZ_CHECK(test_icu_sortmap("da_DK", da_1_len, da_1_src, da_1_cck));
271     }
272     /* successful tests */
273     {
274         size_t de_1_len = 9;
275         const char *de_1_src[9] = {"u", "ä", "o", "t", "s", "ß", "ü", "ö", "a"};
276         const char *de_1_cck[9] = {"a","ä", "o", "ö", "s", "ß", "t", "u", "ü"};
277         YAZ_CHECK(test_icu_sortmap("de", de_1_len, de_1_src, de_1_cck));
278         YAZ_CHECK(test_icu_sortmap("de_AT", de_1_len, de_1_src, de_1_cck));
279         YAZ_CHECK(test_icu_sortmap("de_DE", de_1_len, de_1_src, de_1_cck));
280     }
281 }
282
283 static int test_icu_normalizer(const char *rules8cstr,
284                                const char *src8cstr,
285                                const char *chk8cstr)
286 {
287     int success = 0;
288
289     UErrorCode status = U_ZERO_ERROR;
290
291     struct icu_buf_utf16 *src16 = icu_buf_utf16_create(0);
292     struct icu_buf_utf16 *dest16 = icu_buf_utf16_create(0);
293     struct icu_buf_utf8 *dest8 = icu_buf_utf8_create(0);
294     struct icu_transform *transform
295         = icu_transform_create(rules8cstr, 'f', 0, &status);
296     icu_check_status(status);
297
298     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
299     icu_check_status(status);
300
301     icu_transform_trans(transform, dest16, src16, &status);
302     icu_check_status(status);
303
304     icu_utf16_to_utf8(dest8, dest16, &status);
305     icu_check_status(status);
306
307
308     if (!strcmp((const char *) dest8->utf8,
309                (const char *) chk8cstr))
310         success = 1;
311     else
312     {
313         success = 0;
314         yaz_log(YLOG_LOG, "Normalization");
315         yaz_log(YLOG_LOG, " Rules:      '%s'", rules8cstr);
316         yaz_log(YLOG_LOG, " Input:      '%s'", src8cstr);
317         yaz_log(YLOG_LOG, " Normalized: '%s'", dest8->utf8);
318         yaz_log(YLOG_LOG, " Expected:   '%s'", chk8cstr);
319     }
320
321     icu_transform_destroy(transform);
322     icu_buf_utf16_destroy(src16);
323     icu_buf_utf16_destroy(dest16);
324     icu_buf_utf8_destroy(dest8);
325
326     return success;
327 }
328
329 static void check_icu_normalizer(void)
330 {
331     YAZ_CHECK(test_icu_normalizer("[:Punctuation:] Any-Remove",
332                                   "Don't shoot!",
333                                   "Dont shoot"));
334
335     YAZ_CHECK(test_icu_normalizer("[:Control:] Any-Remove",
336                                   "Don't\n shoot!",
337                                   "Don't shoot!"));
338
339     YAZ_CHECK(test_icu_normalizer("[:Decimal_Number:] Any-Remove",
340                                   "This is 4 you!",
341                                   "This is  you!"));
342
343     YAZ_CHECK(test_icu_normalizer("Lower; [:^Letter:] Remove",
344                                   "Don't shoot!",
345                                   "dontshoot"));
346
347     YAZ_CHECK(test_icu_normalizer("[:^Number:] Remove",
348                                   "Monday 15th of April",
349                                   "15"));
350
351     YAZ_CHECK(test_icu_normalizer("Lower;"
352                                   "[[:WhiteSpace:][:Punctuation:]] Remove",
353                                   " word4you? ",
354                                   "word4you"));
355
356     YAZ_CHECK(test_icu_normalizer("NFD; [:Nonspacing Mark:] Remove; NFC",
357                                   "à côté de l'alcôve ovoïde",
358                                   "a cote de l'alcove ovoide"));
359 }
360
361 static int test_icu_tokenizer(const char *locale, char action,
362                               const char *src8cstr, int count)
363 {
364     int success = 1;
365
366     UErrorCode status = U_ZERO_ERROR;
367     struct icu_buf_utf16 *src16 = icu_buf_utf16_create(0);
368     struct icu_buf_utf16 *tkn16 = icu_buf_utf16_create(0);
369     struct icu_buf_utf8 *tkn8 = icu_buf_utf8_create(0);
370     struct icu_tokenizer *tokenizer = 0;
371     size_t org_start, org_len;
372
373     /* transforming to UTF16 */
374     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
375     icu_check_status(status);
376
377     /* set up tokenizer */
378     tokenizer = icu_tokenizer_create(locale, action, &status);
379     icu_check_status(status);
380     YAZ_CHECK(tokenizer);
381
382     /* attach text buffer to tokenizer */
383     icu_tokenizer_attach(tokenizer, src16, &status);
384     icu_check_status(status);
385
386     /* perform work on tokens */
387     while (icu_tokenizer_next_token(tokenizer, tkn16, &status,
388                                     &org_start, &org_len))
389     {
390         icu_check_status(status);
391
392         /* converting to UTF8 */
393         icu_utf16_to_utf8(tkn8, tkn16, &status);
394     }
395
396     if (count != icu_tokenizer_token_count(tokenizer))
397     {
398         success = 0;
399         yaz_log(YLOG_LOG, "Tokenizer '%s:%c' Error:", locale, action);
400         yaz_log(YLOG_LOG, " Input:  '%s'", src8cstr);
401         yaz_log(YLOG_LOG, " Tokens: %d", icu_tokenizer_token_count(tokenizer));
402         yaz_log(YLOG_LOG, " Expected: %d", count);
403     }
404
405     icu_tokenizer_destroy(tokenizer);
406     icu_buf_utf16_destroy(src16);
407     icu_buf_utf16_destroy(tkn16);
408     icu_buf_utf8_destroy(tkn8);
409
410     return success;
411 }
412
413 static void check_icu_tokenizer(void)
414 {
415     const char *en_str
416         = "O Romeo, Romeo! wherefore art thou Romeo?";
417
418     YAZ_CHECK(test_icu_tokenizer("en", 's', en_str, 2));
419     YAZ_CHECK(test_icu_tokenizer("en", 'l', en_str, 7));
420     YAZ_CHECK(test_icu_tokenizer("en", 'w', en_str, 16));
421     YAZ_CHECK(test_icu_tokenizer("en", 'c', en_str, 41));
422
423     {
424         const char *da_str
425             = "Blåbærtærte. Denne kage stammer fra Finland. "
426             "Den er med blåbær, men alle sommerens forskellige bær kan bruges.";
427
428         YAZ_CHECK(test_icu_tokenizer("da", 's', da_str, 3));
429         YAZ_CHECK(test_icu_tokenizer("dar", 'l', da_str, 17));
430         YAZ_CHECK(test_icu_tokenizer("da", 'w', da_str, 37));
431         YAZ_CHECK(test_icu_tokenizer("da", 'c', da_str, 110));
432     }
433 }
434
435 static void check_icu_chain(void)
436 {
437     const char *en_str
438         = "O Romeo, Romeo! wherefore art thou\t Romeo?";
439
440     UErrorCode status = U_ZERO_ERROR;
441     struct icu_chain *chain = 0;
442
443     const char *xml_str = "<icu locale=\"en\">"
444         "<transform rule=\"[:Control:] Any-Remove\"/>"
445         "<tokenize rule=\"l\"/>"
446         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
447         "<display/>"
448         "<casemap rule=\"l\"/>"
449         "</icu>";
450
451
452     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
453     xmlNode *xml_node = xmlDocGetRootElement(doc);
454     YAZ_CHECK(xml_node);
455
456     chain = icu_chain_xml_config(xml_node, 0, &status);
457
458     xmlFreeDoc(doc);
459     YAZ_CHECK(chain);
460     if (!chain)
461         return;
462
463     YAZ_CHECK(icu_chain_assign_cstr(chain, en_str, &status));
464
465     while (icu_chain_next_token(chain, &status))
466     {
467         yaz_log(YLOG_LOG, "%d '%s' '%s'",
468                 icu_chain_token_number(chain),
469                 icu_chain_token_norm(chain),
470                 icu_chain_token_display(chain));
471     }
472
473     YAZ_CHECK_EQ(icu_chain_token_number(chain), 7);
474
475
476     YAZ_CHECK(icu_chain_assign_cstr(chain, "what is this?", &status));
477
478     while (icu_chain_next_token(chain, &status))
479     {
480         yaz_log(YLOG_LOG, "%d '%s' '%s'",
481                 icu_chain_token_number(chain),
482                 icu_chain_token_norm(chain),
483                 icu_chain_token_display(chain));
484     }
485
486
487     YAZ_CHECK_EQ(icu_chain_token_number(chain), 3);
488
489     icu_chain_destroy(chain);
490 }
491
492
493 static void check_bug_1140(void)
494 {
495     UErrorCode status = U_ZERO_ERROR;
496     struct icu_chain *chain = 0;
497
498     const char *xml_str = "<icu locale=\"en\">"
499
500         /* if the first rule is normalize instead. Then it works */
501 #if 0
502         "<transform rule=\"[:Control:] Any-Remove\"/>"
503 #endif
504         "<tokenize rule=\"l\"/>"
505         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
506         "<display/>"
507         "<casemap rule=\"l\"/>"
508         "</icu>";
509
510
511     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
512     xmlNode *xml_node = xmlDocGetRootElement(doc);
513     YAZ_CHECK(xml_node);
514
515     chain = icu_chain_xml_config(xml_node, 0, &status);
516
517     xmlFreeDoc(doc);
518     YAZ_CHECK(chain);
519     if (!chain)
520         return;
521
522     YAZ_CHECK(icu_chain_assign_cstr(
523                   chain,  "O Romeo, Romeo! wherefore art thou\t Romeo?",
524                   &status));
525
526     while (icu_chain_next_token(chain, &status))
527     {
528         ;
529         /* printf("%d '%s' '%s'\n",
530            icu_chain_token_number(chain),
531            icu_chain_token_norm(chain),
532            icu_chain_token_display(chain)); */
533     }
534
535
536     YAZ_CHECK_EQ(icu_chain_token_number(chain), 7);
537
538     YAZ_CHECK(icu_chain_assign_cstr(chain, "what is this?", &status));
539
540     while (icu_chain_next_token(chain, &status))
541     {
542         ;
543         /* printf("%d '%s' '%s'\n",
544            icu_chain_token_number(chain),
545            icu_chain_token_norm(chain),
546            icu_chain_token_display(chain)); */
547     }
548
549     /* we expect 'what' 'is' 'this', i.e. 3 tokens */
550     YAZ_CHECK_EQ(icu_chain_token_number(chain), 3);
551
552     icu_chain_destroy(chain);
553 }
554
555
556 static void check_chain_empty_token(void)
557 {
558     UErrorCode status = U_ZERO_ERROR;
559     struct icu_chain *chain = 0;
560
561     const char *xml_str = "<icu locale=\"en\">"
562         "<tokenize rule=\"w\"/>"
563         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
564         "</icu>";
565
566     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
567     xmlNode *xml_node = xmlDocGetRootElement(doc);
568     YAZ_CHECK(xml_node);
569
570     chain = icu_chain_xml_config(xml_node, 0, &status);
571
572     xmlFreeDoc(doc);
573     YAZ_CHECK(chain);
574
575     YAZ_CHECK(icu_chain_assign_cstr(
576                   chain,  "a string with 15 tokenss and 8 displays",
577                   &status));
578
579     while (icu_chain_next_token(chain, &status))
580     {
581         ;
582         /* printf("%d '%s' '%s'\n",
583            icu_chain_token_number(chain),
584            icu_chain_token_norm(chain),
585            icu_chain_token_display(chain)); */
586     }
587
588     YAZ_CHECK_EQ(icu_chain_token_number(chain), 15);
589
590     icu_chain_destroy(chain);
591 }
592
593 static void check_chain_empty_chain(void)
594 {
595     UErrorCode status = U_ZERO_ERROR;
596     struct icu_chain *chain = 0;
597
598     const char *xml_str = "<icu locale=\"en\">"
599         "</icu>";
600
601     const char *src8 = "some 5487 weired !¤%&(/& sTuFf";
602     char *dest8 = 0;
603
604     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
605     xmlNode *xml_node = xmlDocGetRootElement(doc);
606     YAZ_CHECK(xml_node);
607
608     chain = icu_chain_xml_config(xml_node, 0, &status);
609
610     xmlFreeDoc(doc);
611     YAZ_CHECK(chain);
612
613     YAZ_CHECK(icu_chain_assign_cstr(
614                   chain,  src8,
615                   &status));
616
617     while (icu_chain_next_token(chain, &status))
618     {
619         ;
620         /* printf("%d '%s' '%s'\n",
621            icu_chain_token_number(chain),
622            icu_chain_token_norm(chain),
623            icu_chain_token_display(chain)); */
624     }
625
626     YAZ_CHECK_EQ(icu_chain_token_number(chain), 1);
627
628     dest8 = (char *) icu_chain_token_norm(chain);
629     YAZ_CHECK_EQ(strcmp(src8, dest8), 0);
630
631     icu_chain_destroy(chain);
632 }
633
634 static void check_icu_iter1(void)
635 {
636     UErrorCode status = U_ZERO_ERROR;
637     struct icu_chain *chain = 0;
638     xmlNode *xml_node;
639     yaz_icu_iter_t iter;
640
641     const char *xml_str = "<icu locale=\"en\">"
642         "<tokenize rule=\"w\"/>"
643         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
644         "</icu>";
645
646     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
647     YAZ_CHECK(doc);
648     if (!doc)
649         return;
650     xml_node = xmlDocGetRootElement(doc);
651     YAZ_CHECK(xml_node);
652     if (!xml_node)
653         return ;
654
655     chain = icu_chain_xml_config(xml_node, 1, &status);
656
657     xmlFreeDoc(doc);
658     YAZ_CHECK(chain);
659
660     iter = icu_iter_create(chain);
661     icu_iter_first(iter, "a string with 15 tokens and 8 displays");
662     YAZ_CHECK(iter);
663     if (!iter)
664         return;
665     while (icu_iter_next(iter))
666     {
667         yaz_log(YLOG_LOG, "[%s]", icu_iter_get_norm(iter));
668     }
669     icu_iter_destroy(iter);
670     icu_chain_destroy(chain);
671 }
672
673 static int test_iter(struct icu_chain *chain, const char *input,
674                      const char *expected)
675 {
676     yaz_icu_iter_t iter = icu_iter_create(chain);
677     WRBUF result, second, sort_result;
678     int success = 1;
679
680     if (!iter)
681     {
682         yaz_log(YLOG_WARN, "test_iter: input=%s !iter", input);
683         return 0;
684     }
685
686     if (icu_iter_next(iter))
687     {
688         yaz_log(YLOG_WARN, "test_iter: expecting 0 before icu_iter_first");
689         return 0;
690     }
691
692     sort_result = wrbuf_alloc();
693     result = wrbuf_alloc();
694     icu_iter_first(iter, input);
695     while (icu_iter_next(iter))
696     {
697         const char *sort_str = icu_iter_get_sortkey(iter);
698         if (sort_str)
699         {
700             wrbuf_puts(sort_result, "[");
701             wrbuf_puts_escaped(sort_result, sort_str);
702             wrbuf_puts(sort_result, "]");
703         }
704         else
705         {
706             wrbuf_puts(sort_result, "[NULL]");
707         }
708         wrbuf_puts(result, "[");
709         wrbuf_puts(result, icu_iter_get_norm(iter));
710         wrbuf_puts(result, "]");
711     }
712     yaz_log(YLOG_LOG, "sortkey=%s", wrbuf_cstr(sort_result));
713     second = wrbuf_alloc();
714     icu_iter_first(iter, input);
715     while (icu_iter_next(iter))
716     {
717         wrbuf_puts(second, "[");
718         wrbuf_puts(second, icu_iter_get_norm(iter));
719         wrbuf_puts(second, "]");
720     }
721
722     icu_iter_destroy(iter);
723
724     if (strcmp(expected, wrbuf_cstr(result)))
725     {
726         yaz_log(YLOG_WARN, "test_iter: input=%s expected=%s got=%s",
727                 input, expected, wrbuf_cstr(result));
728         success = 0;
729     }
730
731     if (strcmp(expected, wrbuf_cstr(second)))
732     {
733         yaz_log(YLOG_WARN, "test_iter: input=%s expected=%s got=%s (2nd)",
734                 input, expected, wrbuf_cstr(second));
735         success = 0;
736     }
737
738     wrbuf_destroy(result);
739     wrbuf_destroy(second);
740     wrbuf_destroy(sort_result);
741     return success;
742 }
743
744 static void *iter_thread(void *p)
745 {
746     struct icu_chain *chain = (struct icu_chain *) p;
747     int i;
748
749     for (i = 0; i < 1000; i++)
750     {
751         YAZ_CHECK(test_iter(chain, "Adobe Acrobat Reader, 1991-1999.",
752                             "[adobe][acrobat][reader][1991][][1999][]"));
753     }
754     return 0;
755 }
756
757 static void check_iter_threads(struct icu_chain *chain)
758 {
759 #if YAZ_POSIX_THREADS
760 #define NO_THREADS 1
761
762     pthread_t t[NO_THREADS];
763     int i;
764
765     for (i = 0; i < NO_THREADS; i++)
766         pthread_create(t + i, 0, iter_thread, chain);
767
768     for (i = 0; i < NO_THREADS; i++)
769         pthread_join(t[i], 0);
770 #endif
771 }
772
773 static void check_icu_iter2(void)
774 {
775     UErrorCode status = U_ZERO_ERROR;
776     struct icu_chain *chain = 0;
777     xmlNode *xml_node;
778
779     const char *xml_str = "<icu locale=\"en\">"
780         "<transform rule=\"[:Control:] Any-Remove\"/>"
781         "<tokenize rule=\"l\"/>"
782         "<tokenize rule=\"w\"/>"
783         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
784         "<display/>"
785         "<casemap rule=\"l\"/>"
786         "</icu>";
787
788     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
789     YAZ_CHECK(doc);
790     if (!doc)
791         return;
792     xml_node = xmlDocGetRootElement(doc);
793     YAZ_CHECK(xml_node);
794     if (!xml_node)
795         return ;
796
797     chain = icu_chain_xml_config(xml_node, 1, &status);
798
799     xmlFreeDoc(doc);
800     YAZ_CHECK(chain);
801     if (!chain)
802         return;
803
804     YAZ_CHECK(test_iter(chain, "Adobe Acrobat Reader, 1991-1999.",
805                         "[adobe][acrobat][reader][1991][][1999][]"));
806
807     YAZ_CHECK(test_iter(chain, "Νόταρης, Γιάννης Σωτ",
808                         "[νόταρης][γιάννης][σωτ]"));
809
810     check_iter_threads(chain);
811
812     icu_chain_destroy(chain);
813 }
814
815 static void check_icu_iter3(void)
816 {
817     UErrorCode status = U_ZERO_ERROR;
818     struct icu_chain *chain = 0;
819     xmlNode *xml_node;
820
821     const char *xml_str =
822         "<icu_chain id=\"sort\" locale=\"el\">\n"
823         "<transform rule=\"[:Control:] Any-Remove\"/>\n"
824         "<transform rule=\"[[:Control:][:WhiteSpace:][:Punctuation:]] Remove\"/>\n"
825         "<transform rule=\"NFD; [:Nonspacing Mark:] Remove; NFC\"/>\n"
826         "<casemap rule=\"l\"/>\n"
827         "<display/>\n"
828         "</icu_chain>\n";
829
830     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
831     YAZ_CHECK(doc);
832     if (!doc)
833         return;
834     xml_node = xmlDocGetRootElement(doc);
835     YAZ_CHECK(xml_node);
836     if (!xml_node)
837         return ;
838
839     chain = icu_chain_xml_config(xml_node, 1, &status);
840
841     xmlFreeDoc(doc);
842     YAZ_CHECK(chain);
843     if (!chain)
844         return;
845
846     YAZ_CHECK(test_iter(chain, "Adobe Acrobat Reader, 1991-1999.",
847                         "[adobeacrobatreader19911999]"));
848
849     YAZ_CHECK(test_iter(chain, "Νόταρης, Γιάννης Σωτ",
850                         "[νοταρηςγιαννηςσωτ]"));
851
852     icu_chain_destroy(chain);
853 }
854
855
856 static void check_icu_iter4(void)
857 {
858     UErrorCode status = U_ZERO_ERROR;
859     struct icu_chain *chain = 0;
860     xmlNode *xml_node;
861
862     const char *xml_str = "<icu locale=\"en\">"
863         "<transform rule=\"[:Control:] Any-Remove\"/>"
864         "<tokenize rule=\"l\"/>"
865         "<tokenize rule=\"w\"/>"
866         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
867         "<display/>"
868         "<casemap rule=\"l\"/>"
869         "<join rule=\"\"/>"
870         "</icu>";
871
872     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
873     YAZ_CHECK(doc);
874     if (!doc)
875         return;
876     xml_node = xmlDocGetRootElement(doc);
877     YAZ_CHECK(xml_node);
878     if (!xml_node)
879         return ;
880
881     chain = icu_chain_xml_config(xml_node, 1, &status);
882
883     xmlFreeDoc(doc);
884     YAZ_CHECK(chain);
885     if (!chain)
886         return;
887
888     YAZ_CHECK(test_iter(chain, "Adobe Acrobat Reader, 1991-1999.",
889                         "[adobeacrobatreader19911999]"));
890
891     YAZ_CHECK(test_iter(chain, "Νόταρης, Γιάννης Σωτ",
892                         "[νόταρηςγιάννηςσωτ]"));
893
894     // check_iter_threads(chain);
895
896     icu_chain_destroy(chain);
897 }
898
899
900 static void check_norm(void)
901 {
902     UErrorCode status = U_ZERO_ERROR;
903     struct icu_chain *chain = 0;
904     xmlNode *xml_node;
905     yaz_icu_iter_t it;
906
907     const char *xml_str =
908         "  <icu_chain id=\"relevance\" locale=\"en\">"
909         "    <transform rule=\"[:Control:] Any-Remove\"/>"
910         "    <tokenize rule=\"l\"/>"
911         "    <transform rule=\"[[:WhiteSpace:][:Punctuation:]`] Remove\"/>"
912         "    <casemap rule=\"l\"/>"
913         "  </icu_chain>";
914
915     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
916     YAZ_CHECK(doc);
917     if (!doc)
918         return;
919     xml_node = xmlDocGetRootElement(doc);
920     YAZ_CHECK(xml_node);
921     if (!xml_node)
922         return ;
923     chain = icu_chain_xml_config(xml_node, 1, &status);
924
925     it = icu_iter_create(chain);
926     if (it)
927     {
928         icu_iter_first(it, " y😄");
929         while (icu_iter_next(it))
930         {
931             const char *norm_str = icu_iter_get_norm(it);
932             size_t start, len;
933
934             YAZ_CHECK(norm_str);
935             if (norm_str)
936                 yaz_log(YLOG_LOG, "norm_str len=%ld=%s",
937                         (long) strlen(norm_str), norm_str);
938             icu_iter_get_org_info(it, &start, &len);
939             YAZ_CHECK(start <= 1000);
940             YAZ_CHECK(len <= 1000);
941         }
942
943         icu_iter_first(it, "\n y😄");
944         while (icu_iter_next(it))
945         {
946             const char *norm_str = icu_iter_get_norm(it);
947             size_t start, len;
948
949             YAZ_CHECK(norm_str);
950             if (norm_str)
951                 yaz_log(YLOG_LOG, "norm_str len=%ld=%s",
952                         (long) strlen(norm_str), norm_str);
953             icu_iter_get_org_info(it, &start, &len);
954             YAZ_CHECK(start <= 1000);
955             YAZ_CHECK(len <= 1000);
956         }
957     }
958     icu_iter_destroy(it);
959     icu_chain_destroy(chain);
960     xmlFreeDoc(doc);
961 }
962 #endif /* YAZ_HAVE_ICU */
963
964 int main(int argc, char **argv)
965 {
966     YAZ_CHECK_INIT(argc, argv);
967     YAZ_CHECK_LOG();
968
969 #if YAZ_HAVE_ICU
970
971     check_icu_casemap();
972     check_icu_sortmap();
973     check_icu_normalizer();
974     check_icu_tokenizer();
975     check_icu_chain();
976     check_chain_empty_token();
977     check_chain_empty_chain();
978     check_icu_iter1();
979     check_icu_iter2();
980     check_icu_iter3();
981     check_icu_iter4();
982
983     check_bug_1140();
984     check_norm();
985
986     u_cleanup();
987 #if YAZ_HAVE_XML2
988     xmlCleanupParser();
989 #endif
990
991 #else /* YAZ_HAVE_ICU */
992
993     yaz_log(YLOG_LOG, "ICU unit tests omitted");
994     YAZ_CHECK(0 == 0);
995
996 #endif /* YAZ_HAVE_ICU */
997
998     YAZ_CHECK_TERM;
999 }
1000
1001 /*
1002  * Local variables:
1003  * c-basic-offset: 4
1004  * c-file-style: "Stroustrup"
1005  * indent-tabs-mode: nil
1006  * End:
1007  * vim: shiftwidth=4 tabstop=8 expandtab
1008  */
1009