bbdc9937e386150f9876dbe0227365d1a67fa299
[yaz-moved-to-github.git] / test / tst_icu_I18N.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2009 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
20 #if YAZ_HAVE_ICU
21 #include <yaz/icu_I18N.h>
22
23 #include <string.h>
24 #include <stdlib.h>
25
26 #define MAX_KEY_SIZE 256
27 struct icu_termmap
28 {
29     uint8_t sort_key[MAX_KEY_SIZE]; /* standard C string '\0' terminated */
30     char disp_term[MAX_KEY_SIZE];  /* standard C utf-8 string */
31 };
32
33
34 int icu_termmap_cmp(const void *vp1, const void *vp2)
35 {
36     struct icu_termmap *itmp1 = *(struct icu_termmap **) vp1;
37     struct icu_termmap *itmp2 = *(struct icu_termmap **) vp2;
38
39     int cmp = 0;
40     
41     cmp = strcmp((const char *)itmp1->sort_key, 
42                  (const char *)itmp2->sort_key);
43     return cmp;
44 }
45
46
47 int test_icu_casemap(const char * locale, char action,
48                      const char * src8cstr, const char * chk8cstr)
49 {
50     int success = 0;
51     UErrorCode status = U_ZERO_ERROR;
52
53     struct icu_buf_utf8 * src8 = icu_buf_utf8_create(0);
54     struct icu_buf_utf8 * dest8 = icu_buf_utf8_create(0);
55     struct icu_buf_utf16 * src16 = icu_buf_utf16_create(0);
56     struct icu_buf_utf16 * dest16 = icu_buf_utf16_create(0);
57
58
59     int src8cstr_len = strlen(src8cstr);
60     int chk8cstr_len = strlen(chk8cstr);
61
62     /* converting to UTF16 */
63     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
64
65     /* perform case mapping */
66     icu_utf16_casemap(dest16, src16, locale, action, &status);
67   
68     /* converting to UTF8 */
69     icu_utf16_to_utf8(dest8, dest16, &status);
70
71     /* determine success */
72     if (dest8->utf8 
73         && (dest8->utf8_len == strlen(chk8cstr))
74         && !strcmp(chk8cstr, (const char *) dest8->utf8))
75         success = 1;
76     else
77         success = 0;
78
79     /* report failures */
80     if (!success){
81         printf("\nERROR\n");
82         printf("original string:   '%s' (%d)\n", src8cstr, src8cstr_len);
83         printf("icu_casemap '%s:%c' '%s' (%d)\n", 
84                locale, action, dest8->utf8, dest8->utf8_len);
85         printf("expected string:   '%s' (%d)\n", chk8cstr, chk8cstr_len);
86     }
87   
88     /* clean the buffers */
89     icu_buf_utf8_destroy(src8);
90     icu_buf_utf8_destroy(dest8);
91     icu_buf_utf16_destroy(src16);
92     icu_buf_utf16_destroy(dest16);
93     
94     return success;
95 }
96
97 void test_icu_I18N_casemap(int argc, char **argv)
98 {
99     /* Locale 'en' */
100
101     /* successful tests */
102     YAZ_CHECK(test_icu_casemap("en", 'l',
103                                "A ReD fOx hunTS sQUirriLs", 
104                                "a red fox hunts squirrils"));
105     
106     YAZ_CHECK(test_icu_casemap("en", 'u',
107                                "A ReD fOx hunTS sQUirriLs", 
108                                "A RED FOX HUNTS SQUIRRILS"));
109     
110     YAZ_CHECK(test_icu_casemap("en", 'f',
111                                "A ReD fOx hunTS sQUirriLs", 
112                                "a red fox hunts squirrils"));
113     
114     YAZ_CHECK(test_icu_casemap("en", 't',
115                                "A ReD fOx hunTS sQUirriLs", 
116                                "A Red Fox Hunts Squirrils"));
117     
118     /* Locale 'da' */
119
120     /* success expected */
121     YAZ_CHECK(test_icu_casemap("da", 'l',
122                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN", 
123                                "åh æble, øs fløde i åen efter blåbærgrøden"));
124
125     YAZ_CHECK(test_icu_casemap("da", 'u',
126                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN", 
127                                "ÅH ÆBLE, ØS FLØDE I ÅEN EFTER BLÅBÆRGRØDEN"));
128
129     YAZ_CHECK(test_icu_casemap("da", 'f',
130                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN", 
131                                "åh æble, øs fløde i åen efter blåbærgrøden"));
132
133     YAZ_CHECK(test_icu_casemap("da", 't',
134                                "åh ÆbLE, øs fLØde i Åen efter bLåBærGRødeN", 
135                                "Åh Æble, Øs Fløde I Åen Efter Blåbærgrøden"));
136
137     /* Locale 'de' */
138
139     /* success expected */
140     YAZ_CHECK(test_icu_casemap("de", 'l',
141                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
142                                "zwölf ärgerliche würste rollen über die straße"));
143
144     YAZ_CHECK(test_icu_casemap("de", 'u',
145                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
146                                "ZWÖLF ÄRGERLICHE WÜRSTE ROLLEN ÜBER DIE STRASSE"));
147
148     YAZ_CHECK(test_icu_casemap("de", 'f',
149                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
150                                "zwölf ärgerliche würste rollen über die strasse"));
151
152     YAZ_CHECK(test_icu_casemap("de", 't',
153                                "zWÖlf ärgerliche Würste rollen ÜBer die StRAße",
154                                "Zwölf Ärgerliche Würste Rollen Über Die Straße"));
155
156 }
157
158 int test_icu_sortmap(const char * locale, int src_list_len,
159                      const char ** src_list, const char ** chk_list)
160 {
161     int success = 1;
162
163     UErrorCode status = U_ZERO_ERROR;
164
165     struct icu_buf_utf8 * buf8 = icu_buf_utf8_create(0);
166     struct icu_buf_utf16 * buf16 = icu_buf_utf16_create(0);
167
168     int i;
169
170     struct icu_termmap * list[src_list_len];
171
172     UCollator *coll = ucol_open(locale, &status); 
173     icu_check_status(status);
174
175     if(U_FAILURE(status))
176         return 0;
177
178     /* assigning display terms and sort keys using buf 8 and buf16 */
179     for (i = 0; i < src_list_len; i++) 
180     {
181
182         list[i] = (struct icu_termmap *) malloc(sizeof(struct icu_termmap));
183
184         /* copy display term */
185         strcpy(list[i]->disp_term, src_list[i]);    
186
187         /* transforming to UTF16 */
188         icu_utf16_from_utf8_cstr(buf16, list[i]->disp_term, &status);
189         icu_check_status(status);
190
191         /* computing sortkeys */
192         icu_sortkey8_from_utf16(coll, buf8, buf16, &status);
193         icu_check_status(status);
194     
195         /* assigning sortkeys */
196         memcpy(list[i]->sort_key, buf8->utf8, buf8->utf8_len);    
197     } 
198
199     /* do the sorting */
200     qsort(list, src_list_len, sizeof(struct icu_termmap *), icu_termmap_cmp);
201
202     /* checking correct sorting */
203     for (i = 0; i < src_list_len; i++)
204     {
205         if (0 != strcmp(list[i]->disp_term, chk_list[i])){
206             success = 0;
207         }
208     }
209
210     if (!success)
211     {
212         printf("\nERROR\n"); 
213         printf("Input str: '%s' : ", locale); 
214         for (i = 0; i < src_list_len; i++) {
215             printf(" '%s'", list[i]->disp_term); 
216         }
217         printf("\n");
218         printf("ICU sort:  '%s' : ", locale); 
219         for (i = 0; i < src_list_len; i++) {
220             printf(" '%s'", list[i]->disp_term); 
221         }
222         printf("\n"); 
223         printf("Expected:  '%s' : ", locale); 
224         for (i = 0; i < src_list_len; i++) {
225             printf(" '%s'", chk_list[i]); 
226         }
227         printf("\n"); 
228     }
229   
230     for (i = 0; i < src_list_len; i++)
231         free(list[i]);
232    
233     ucol_close(coll);
234
235     icu_buf_utf8_destroy(buf8);
236     icu_buf_utf16_destroy(buf16);
237
238     return success;  
239 }
240
241 void test_icu_I18N_sortmap(int argc, char **argv)
242 {
243     /* successful tests */
244     size_t en_1_len = 6;
245     const char * en_1_src[6] = {"z", "K", "a", "A", "Z", "k"};
246     const char * en_1_cck[6] = {"a", "A", "k", "K", "z", "Z"};
247     YAZ_CHECK(test_icu_sortmap("en", en_1_len, en_1_src, en_1_cck));
248     YAZ_CHECK(test_icu_sortmap("en_AU", en_1_len, en_1_src, en_1_cck));
249     YAZ_CHECK(test_icu_sortmap("en_CA", en_1_len, en_1_src, en_1_cck));
250     YAZ_CHECK(test_icu_sortmap("en_GB", en_1_len, en_1_src, en_1_cck));
251     YAZ_CHECK(test_icu_sortmap("en_US", en_1_len, en_1_src, en_1_cck));
252     
253     /* successful tests */
254     {
255         size_t da_1_len = 6;
256         const char * da_1_src[6] = {"z", "å", "o", "æ", "a", "ø"};
257         const char * da_1_cck[6] = {"a", "o", "z", "æ", "ø", "å"};
258         YAZ_CHECK(test_icu_sortmap("da", da_1_len, da_1_src, da_1_cck));
259         YAZ_CHECK(test_icu_sortmap("da_DK", da_1_len, da_1_src, da_1_cck));
260     }
261     /* successful tests */
262     {
263         size_t de_1_len = 9;
264         const char * de_1_src[9] = {"u", "ä", "o", "t", "s", "ß", "ü", "ö", "a"};
265         const char * de_1_cck[9] = {"a","ä", "o", "ö", "s", "ß", "t", "u", "ü"};
266         YAZ_CHECK(test_icu_sortmap("de", de_1_len, de_1_src, de_1_cck));
267         YAZ_CHECK(test_icu_sortmap("de_AT", de_1_len, de_1_src, de_1_cck));
268         YAZ_CHECK(test_icu_sortmap("de_DE", de_1_len, de_1_src, de_1_cck));
269     }
270     
271 }
272
273 int test_icu_normalizer(const char * rules8cstr,
274                         const char * src8cstr,
275                         const char * chk8cstr)
276 {
277     int success = 0;
278     
279     UErrorCode status = U_ZERO_ERROR;
280
281     struct icu_buf_utf16 * src16 = icu_buf_utf16_create(0);
282     struct icu_buf_utf16 * dest16 = icu_buf_utf16_create(0);
283     struct icu_buf_utf8 * dest8 = icu_buf_utf8_create(0);
284     struct icu_transform * transform
285         = icu_transform_create(rules8cstr, 'f', 0, &status);
286     icu_check_status(status);
287     
288     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
289     icu_check_status(status);
290
291     icu_transform_trans(transform, dest16, src16, &status);
292     icu_check_status(status);
293
294     icu_utf16_to_utf8(dest8, dest16, &status);
295     icu_check_status(status);
296
297
298     if(!strcmp((const char *) dest8->utf8, 
299                (const char *) chk8cstr))
300         success = 1;
301     else
302     {
303         success = 0;
304         printf("Normalization\n");
305         printf("Rules:      '%s'\n", rules8cstr);
306         printf("Input:      '%s'\n", src8cstr);
307         printf("Normalized: '%s'\n", dest8->utf8);
308         printf("Expected:   '%s'\n", chk8cstr);
309     }
310
311     icu_transform_destroy(transform);
312     icu_buf_utf16_destroy(src16);
313     icu_buf_utf16_destroy(dest16);
314     icu_buf_utf8_destroy(dest8);
315
316     return success;
317 }
318
319 void test_icu_I18N_normalizer(int argc, char **argv)
320 {
321     YAZ_CHECK(test_icu_normalizer("[:Punctuation:] Any-Remove",
322                                   "Don't shoot!",
323                                   "Dont shoot"));
324     
325     YAZ_CHECK(test_icu_normalizer("[:Control:] Any-Remove",
326                                   "Don't\n shoot!",
327                                   "Don't shoot!"));
328
329     YAZ_CHECK(test_icu_normalizer("[:Decimal_Number:] Any-Remove",
330                                   "This is 4 you!",
331                                   "This is  you!"));
332
333     YAZ_CHECK(test_icu_normalizer("Lower; [:^Letter:] Remove",
334                                   "Don't shoot!",
335                                   "dontshoot"));
336     
337     YAZ_CHECK(test_icu_normalizer("[:^Number:] Remove",
338                                   "Monday 15th of April",
339                                   "15"));
340
341     YAZ_CHECK(test_icu_normalizer("Lower;"
342                                   "[[:WhiteSpace:][:Punctuation:]] Remove",
343                                   " word4you? ",
344                                   "word4you"));
345
346     YAZ_CHECK(test_icu_normalizer("NFD; [:Nonspacing Mark:] Remove; NFC",
347                                   "à côté de l'alcôve ovoïde",
348                                   "a cote de l'alcove ovoide"));
349 }
350
351 int test_icu_tokenizer(const char * locale, char action,
352                        const char * src8cstr, int count)
353 {
354     int success = 1;
355
356     UErrorCode status = U_ZERO_ERROR;
357     struct icu_buf_utf16 * src16 = icu_buf_utf16_create(0);
358     struct icu_buf_utf16 * tkn16 = icu_buf_utf16_create(0);
359     struct icu_buf_utf8 * tkn8 = icu_buf_utf8_create(0);
360     struct icu_tokenizer * tokenizer = 0;
361
362     /* transforming to UTF16 */
363     icu_utf16_from_utf8_cstr(src16, src8cstr, &status);
364     icu_check_status(status);
365
366     /* set up tokenizer */
367     tokenizer = icu_tokenizer_create(locale, action, &status);
368     icu_check_status(status);
369     YAZ_CHECK(tokenizer);
370
371     /* attach text buffer to tokenizer */
372     icu_tokenizer_attach(tokenizer, src16, &status);    
373     icu_check_status(status);
374     YAZ_CHECK(tokenizer->bi);
375
376     /* perform work on tokens */
377     while(icu_tokenizer_next_token(tokenizer, tkn16, &status)){
378         icu_check_status(status);
379
380         /* converting to UTF8 */
381         icu_utf16_to_utf8(tkn8, tkn16, &status);
382     }
383
384     if (count != icu_tokenizer_token_count(tokenizer)){
385         success = 0;
386         printf("\nTokenizer '%s:%c' Error: \n", locale, action);
387         printf("Input:  '%s'\n", src8cstr);
388         printf("Tokens: %d", icu_tokenizer_token_count(tokenizer));
389         printf(", expected: %d\n", count);
390     }
391
392     icu_tokenizer_destroy(tokenizer);
393     icu_buf_utf16_destroy(src16);
394     icu_buf_utf16_destroy(tkn16);
395     icu_buf_utf8_destroy(tkn8);
396         
397     return success;
398 }
399
400 void test_icu_I18N_tokenizer(int argc, char **argv)
401 {
402     const char * en_str 
403         = "O Romeo, Romeo! wherefore art thou Romeo?";
404     
405     YAZ_CHECK(test_icu_tokenizer("en", 's', en_str, 2));
406     YAZ_CHECK(test_icu_tokenizer("en", 'l', en_str, 7));
407     YAZ_CHECK(test_icu_tokenizer("en", 'w', en_str, 16));
408     YAZ_CHECK(test_icu_tokenizer("en", 'c', en_str, 41));
409
410     {
411         const char * da_str 
412             = "Blåbærtærte. Denne kage stammer fra Finland. "
413             "Den er med blåbær, men alle sommerens forskellige bær kan bruges.";
414         
415         YAZ_CHECK(test_icu_tokenizer("da", 's', da_str, 3));
416         YAZ_CHECK(test_icu_tokenizer("dar", 'l', da_str, 17));
417         YAZ_CHECK(test_icu_tokenizer("da", 'w', da_str, 37));
418         YAZ_CHECK(test_icu_tokenizer("da", 'c', da_str, 110));
419     }
420 }
421
422 void test_icu_I18N_chain(int argc, char **argv)
423 {
424     const char * en_str 
425         = "O Romeo, Romeo! wherefore art thou\t Romeo?";
426
427     UErrorCode status = U_ZERO_ERROR;
428     struct icu_chain * chain = 0;
429     
430     const char * xml_str = "<icu locale=\"en\">"
431         "<transform rule=\"[:Control:] Any-Remove\"/>"
432         "<tokenize rule=\"l\"/>"
433         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
434         "<display/>"
435         "<casemap rule=\"l\"/>"
436         "</icu>";
437
438     
439     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
440     xmlNode *xml_node = xmlDocGetRootElement(doc);
441     YAZ_CHECK(xml_node);
442
443     chain = icu_chain_xml_config(xml_node, 0, &status);
444
445     xmlFreeDoc(doc);
446     YAZ_CHECK(chain);
447
448     YAZ_CHECK(icu_chain_assign_cstr(chain, en_str, &status));
449
450     while (icu_chain_next_token(chain, &status))
451     {
452         ;
453         /* printf("%d '%s' '%s'\n",
454            icu_chain_token_number(chain),
455            icu_chain_token_norm(chain),
456            icu_chain_token_display(chain)); */
457     }
458
459     YAZ_CHECK_EQ(icu_chain_token_number(chain), 7);
460
461
462     YAZ_CHECK(icu_chain_assign_cstr(chain, "what is this?", &status));
463
464     while (icu_chain_next_token(chain, &status))
465     {
466         ;
467         /* printf("%d '%s' '%s'\n",
468            icu_chain_token_number(chain),
469            icu_chain_token_norm(chain),
470            icu_chain_token_display(chain)); */
471     }
472
473
474     YAZ_CHECK_EQ(icu_chain_token_number(chain), 3);
475
476     icu_chain_destroy(chain);
477 }
478
479
480 void test_bug_1140(void)
481 {
482     UErrorCode status = U_ZERO_ERROR;
483     struct icu_chain * chain = 0;
484     
485     const char * xml_str = "<icu locale=\"en\">"
486
487         /* if the first rule is normalize instead. Then it works */
488 #if 0
489         "<transform rule=\"[:Control:] Any-Remove\"/>"
490 #endif
491         "<tokenize rule=\"l\"/>"
492         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
493         "<display/>"
494         "<casemap rule=\"l\"/>"
495         "</icu>";
496
497     
498     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
499     xmlNode *xml_node = xmlDocGetRootElement(doc);
500     YAZ_CHECK(xml_node);
501
502     chain = icu_chain_xml_config(xml_node, 0, &status);
503
504     xmlFreeDoc(doc);
505     YAZ_CHECK(chain);
506     
507     YAZ_CHECK(icu_chain_assign_cstr(
508                   chain,  "O Romeo, Romeo! wherefore art thou\t Romeo?",
509                   &status));
510
511     while (icu_chain_next_token(chain, &status))
512     {
513         ;
514         /* printf("%d '%s' '%s'\n",
515            icu_chain_token_number(chain),
516            icu_chain_token_norm(chain),
517            icu_chain_token_display(chain)); */
518     }
519     
520
521     YAZ_CHECK_EQ(icu_chain_token_number(chain), 7);
522
523     YAZ_CHECK(icu_chain_assign_cstr(chain, "what is this?", &status));
524
525     while (icu_chain_next_token(chain, &status))
526     {
527         ;
528         /* printf("%d '%s' '%s'\n",
529            icu_chain_token_number(chain),
530            icu_chain_token_norm(chain),
531            icu_chain_token_display(chain)); */
532     }
533
534     /* we expect 'what' 'is' 'this', i.e. 3 tokens */
535     YAZ_CHECK_EQ(icu_chain_token_number(chain), 3);
536
537     icu_chain_destroy(chain);
538 }
539
540
541 void test_chain_empty_token(void)
542 {
543     UErrorCode status = U_ZERO_ERROR;
544     struct icu_chain * chain = 0;
545
546     const char * xml_str = "<icu locale=\"en\">"
547         "<tokenize rule=\"w\"/>"
548         "<transform rule=\"[[:WhiteSpace:][:Punctuation:]] Remove\"/>"
549         "</icu>";
550     
551     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
552     xmlNode *xml_node = xmlDocGetRootElement(doc);
553     YAZ_CHECK(xml_node);
554
555     chain = icu_chain_xml_config(xml_node, 0, &status);
556
557     xmlFreeDoc(doc);
558     YAZ_CHECK(chain);
559     
560     YAZ_CHECK(icu_chain_assign_cstr(
561                   chain,  "a string with 15 tokenss and 8 displays",
562                   &status));
563
564     while (icu_chain_next_token(chain, &status))
565     {
566         ;
567         /* printf("%d '%s' '%s'\n",
568            icu_chain_token_number(chain),
569            icu_chain_token_norm(chain),
570            icu_chain_token_display(chain)); */
571     }
572
573     YAZ_CHECK_EQ(icu_chain_token_number(chain), 15);
574
575     icu_chain_destroy(chain);
576 }
577
578 void test_chain_empty_chain(void)
579 {
580     UErrorCode status = U_ZERO_ERROR;
581     struct icu_chain * chain = 0;
582
583     const char * xml_str = "<icu locale=\"en\">"
584         "</icu>";
585     
586     const char * src8 = "some 5487 weired !¤%&(/& sTuFf";
587     char * dest8 = 0;
588
589     xmlDoc *doc = xmlParseMemory(xml_str, strlen(xml_str));
590     xmlNode *xml_node = xmlDocGetRootElement(doc);
591     YAZ_CHECK(xml_node);
592
593     chain = icu_chain_xml_config(xml_node, 0, &status);
594
595     xmlFreeDoc(doc);
596     YAZ_CHECK(chain);
597     
598     YAZ_CHECK(icu_chain_assign_cstr(
599                   chain,  src8,
600                   &status));
601
602     while (icu_chain_next_token(chain, &status))
603     {
604         ;
605         /* printf("%d '%s' '%s'\n",
606            icu_chain_token_number(chain),
607            icu_chain_token_norm(chain),
608            icu_chain_token_display(chain)); */
609     }
610
611     YAZ_CHECK_EQ(icu_chain_token_number(chain), 1);
612
613     dest8 = (char *) icu_chain_token_norm(chain);
614     YAZ_CHECK_EQ(strcmp(src8, dest8), 0);
615     
616     icu_chain_destroy(chain);
617 }
618
619 #endif /* YAZ_HAVE_ICU */
620
621 int main(int argc, char **argv)
622 {
623     YAZ_CHECK_INIT(argc, argv); 
624     YAZ_CHECK_LOG();
625
626 #if YAZ_HAVE_ICU
627
628     test_icu_I18N_casemap(argc, argv);
629     test_icu_I18N_sortmap(argc, argv);
630     test_icu_I18N_normalizer(argc, argv); 
631     test_icu_I18N_tokenizer(argc, argv);
632     test_icu_I18N_chain(argc, argv);
633     test_chain_empty_token();
634     test_chain_empty_chain();
635     test_bug_1140();
636
637 #else /* YAZ_HAVE_ICU */
638
639     printf("ICU unit tests omitted.\n"
640            "Please install libicu36-dev and icu-doc or similar\n");
641     YAZ_CHECK(0 == 0);
642
643 #endif /* YAZ_HAVE_ICU */
644    
645     YAZ_CHECK_TERM;
646 }
647
648 /*
649  * Local variables:
650  * c-basic-offset: 4
651  * c-file-style: "Stroustrup"
652  * indent-tabs-mode: nil
653  * End:
654  * vim: shiftwidth=4 tabstop=8 expandtab
655  */
656