Towards GPL
[idzebra-moved-to-github.git] / util / charmap.c
1 /* $Id: charmap.c,v 1.25 2002-08-02 19:26:57 adam Exp $
2    Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002
3    Index Data Aps
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23
24
25 /*
26  * Support module to handle character-conversions into and out of the
27  * Zebra dictionary.
28  */
29
30 #include <ctype.h>
31 #include <string.h>
32 #include <assert.h>
33
34 #if HAVE_ICONV_H
35 #include <iconv.h>
36 #else
37 typedef int iconv_t;
38 static size_t iconv(iconv_t t, char **buf, size_t *inbytesleft,
39                     char **outbuf, size_t *outbytesleft)
40 {
41     return -1;
42 }
43 #endif
44
45 typedef unsigned ucs4_t;
46
47 #include <yaz/yaz-util.h>
48 #include <charmap.h>
49
50
51 #define CHR_MAXSTR 1024
52 #define CHR_MAXEQUIV 32
53
54 const char *CHR_UNKNOWN = "\001";
55 const char *CHR_SPACE   = "\002";
56 const char *CHR_BASE    = "\003";
57
58 struct chrmaptab_info
59 {
60     chr_t_entry *input;         /* mapping table for input data */
61     chr_t_entry *q_input;       /* mapping table for queries */
62     unsigned char *output[256]; /* return mapping - for display of registers */
63     int base_uppercase;         /* Start of upper-case ordinals */
64     NMEM nmem;
65 };
66
67 /*
68  * Character map trie node.
69  */
70 struct chr_t_entry
71 {
72     chr_t_entry **children;  /* array of children */
73     unsigned char **target;  /* target for this node, if any */
74 };
75
76 /*
77  * General argument structure for callback functions (internal use only)
78  */
79 typedef struct chrwork 
80 {
81     chrmaptab map;
82     char string[CHR_MAXSTR+1];
83 } chrwork;
84
85 /*
86  * Add an entry to the character map.
87  */
88 static chr_t_entry *set_map_string(chr_t_entry *root, NMEM nmem,
89                                    const char *from, int len, char *to,
90                                    const char *from_0)
91 {
92     if (!from_0)
93         from_0 = from;
94     if (!root)
95     {
96         root = (chr_t_entry *) nmem_malloc(nmem, sizeof(*root));
97         root->children = 0;
98         root->target = 0;
99     }
100     if (!len)
101     {
102         if (!root->target || !root->target[0] || strcmp(root->target[0], to))
103         {
104             if (from_0 && 
105                 root->target && root->target[0] && root->target[0][0] &&
106                 strcmp (root->target[0], CHR_UNKNOWN))
107             {
108                 yaz_log (LOG_WARN, "duplicate entry for charmap from '%s'",
109                          from_0);
110             }
111             root->target = (unsigned char **)
112                 nmem_malloc(nmem, sizeof(*root->target)*2);
113             root->target[0] = (unsigned char *) nmem_strdup(nmem, to);
114             root->target[1] = 0;
115         }
116     }
117     else
118     {
119         if (!root->children)
120         {
121             int i;
122
123             root->children = (chr_t_entry **)
124                 nmem_malloc(nmem, sizeof(chr_t_entry*) * 256);
125             for (i = 0; i < 256; i++)
126                 root->children[i] = 0;
127         }
128         if (!(root->children[(unsigned char) *from] =
129             set_map_string(root->children[(unsigned char) *from], nmem,
130                            from + 1, len - 1, to, from_0)))
131             return 0;
132     }
133     return root;
134 }
135
136 static chr_t_entry *find_entry(chr_t_entry *t, const char **from, int len)
137 {
138     chr_t_entry *res;
139
140     if (len && t->children && t->children[(unsigned char) **from])
141     {
142         const char *pos = *from;
143
144         (*from)++;
145         if ((res = find_entry(t->children[(unsigned char) *pos],
146             from, len - 1)))
147             return res;
148         /* no match */
149         *from = pos;
150     }
151     /* no children match. use ourselves, if we have a target */
152     return t->target ? t : 0;
153 }
154
155 static chr_t_entry *find_entry_x(chr_t_entry *t, const char **from, int *len)
156 {
157     chr_t_entry *res;
158
159     while (*len <= 0)
160     {   /* switch to next buffer */
161         if (*len < 0)
162             break;
163         from++;
164         len++;
165     }
166     if (*len > 0 && t->children && t->children[(unsigned char) **from])
167     {
168         const char *old_from = *from;
169         int old_len = *len;
170         
171         (*len)--;
172         (*from)++;
173         if ((res = find_entry_x(t->children[(unsigned char) *old_from],
174                                 from, len)))
175             return res;
176         /* no match */
177         *len = old_len;
178         *from = old_from;
179     }
180     /* no children match. use ourselves, if we have a target */
181     return t->target ? t : 0;
182 }
183
184 const char **chr_map_input_x(chrmaptab maptab, const char **from, int *len)
185 {
186     chr_t_entry *t = maptab->input;
187     chr_t_entry *res;
188
189     if (!(res = find_entry_x(t, from, len)))
190         abort();
191     return (const char **) (res->target);
192 }
193
194 const char **chr_map_input(chrmaptab maptab, const char **from, int len)
195 {
196     chr_t_entry *t = maptab->input;
197     chr_t_entry *res;
198     int len_tmp[2];
199
200     len_tmp[0] = len;
201     len_tmp[1] = -1;
202     if (!(res = find_entry_x(t, from, len_tmp)))
203         abort();
204     return (const char **) (res->target);
205 }
206
207 const char *chr_map_output(chrmaptab maptab, const char **from, int len)
208 {
209     unsigned char c = ** (unsigned char **) from;
210     (*from)++;
211     return (const char*) maptab->output[c];
212 }
213
214 unsigned char zebra_prim(char **s)
215 {
216     unsigned char c;
217     unsigned int i = 0;
218
219     yaz_log (LOG_DEBUG, "prim %.3s", *s);
220     if (**s == '\\')
221     {
222         (*s)++;
223         c = **s;
224         switch (c)
225         {
226         case '\\': c = '\\'; (*s)++; break;
227         case 'r': c = '\r'; (*s)++; break;
228         case 'n': c = '\n'; (*s)++; break;
229         case 't': c = '\t'; (*s)++; break;
230         case 's': c = ' '; (*s)++; break;
231         case 'x': sscanf(*s, "x%2x", &i); c = i; *s += 3; break;
232         case '0':
233         case '1':
234         case '2':
235         case '3':
236         case '4':
237         case '5':
238         case '6':
239         case '7':
240         case '8':
241         case '9':
242             sscanf(*s, "%3o", &i);
243             c = i;
244             *s += 3;
245             break;
246         default:
247             (*s)++;
248         }
249     }
250     else
251     {
252         c = **s;
253         ++(*s);
254     }
255     return c;
256 }
257
258 ucs4_t zebra_prim_w(ucs4_t **s)
259 {
260     ucs4_t c;
261     ucs4_t i = 0;
262     char fmtstr[8];
263
264     yaz_log (LOG_DEBUG, "prim %.3s", (char *) *s);
265     if (**s == '\\')
266     {
267         (*s)++;
268         c = **s;
269         switch (c)
270         {
271         case '\\': c = '\\'; (*s)++; break;
272         case 'r': c = '\r'; (*s)++; break;
273         case 'n': c = '\n'; (*s)++; break;
274         case 't': c = '\t'; (*s)++; break;
275         case 's': c = ' '; (*s)++; break;
276         case 'x': 
277             fmtstr[0] = (*s)[0];
278             fmtstr[1] = (*s)[1];
279             fmtstr[2] = (*s)[2];
280             fmtstr[3] = 0;
281             sscanf(fmtstr, "x%2x", &i);
282             c = i;
283             *s += 3; break;
284         case '0':
285         case '1':
286         case '2':
287         case '3':
288         case '4':
289         case '5':
290         case '6':
291         case '7':
292         case '8':
293         case '9':
294             fmtstr[0] = (*s)[0];
295             fmtstr[1] = (*s)[1];
296             fmtstr[2] = (*s)[2];
297             fmtstr[3] = 0;
298             sscanf(fmtstr, "%3o", &i);
299             c = i;
300             *s += 3;
301             break;
302         default:
303             (*s)++;
304         }
305     }
306     else
307     {
308         c = **s;
309         ++(*s);
310     }
311     yaz_log (LOG_DEBUG, "out %d", c);
312     return c;
313 }
314
315 /*
316  * Callback function.
317  * Add an entry to the value space.
318  */
319 static void fun_addentry(const char *s, void *data, int num)
320 {
321     chrmaptab tab = (chrmaptab) data;
322     char tmp[2];
323     
324     tmp[0] = num; tmp[1] = '\0';
325     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s), tmp, 0);
326     tab->output[num + tab->base_uppercase] =
327         (unsigned char *) nmem_strdup(tab->nmem, s);
328 }
329
330 /* 
331  * Callback function.
332  * Add a space-entry to the value space.
333  */
334 static void fun_addspace(const char *s, void *data, int num)
335 {
336     chrmaptab tab = (chrmaptab) data;
337     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
338                                 (char*) CHR_SPACE, 0);
339 }
340
341 /*
342  * Create a string containing the mapped characters provided.
343  */
344 static void fun_mkstring(const char *s, void *data, int num)
345 {
346     chrwork *arg = (chrwork *) data;
347     const char **res, *p = s;
348
349     res = chr_map_input(arg->map, &s, strlen(s));
350     if (*res == (char*) CHR_UNKNOWN)
351         logf(LOG_WARN, "Map: '%s' has no mapping", p);
352     strncat(arg->string, *res, CHR_MAXSTR - strlen(arg->string));
353     arg->string[CHR_MAXSTR] = '\0';
354 }
355
356 /*
357  * Add a map to the string contained in the argument.
358  */
359 static void fun_add_map(const char *s, void *data, int num)
360 {
361     chrwork *arg = (chrwork *) data;
362
363     assert(arg->map->input);
364     logf (LOG_DEBUG, "set map %.*s", (int) strlen(s), s);
365     set_map_string(arg->map->input, arg->map->nmem, s, strlen(s), arg->string,
366                    0);
367     for (s = arg->string; *s; s++)
368         logf (LOG_DEBUG, " %3d", (unsigned char) *s);
369 }
370
371 /*
372  * Add a query map to the string contained in the argument.
373  */
374 static void fun_add_qmap(const char *s, void *data, int num)
375 {
376     chrwork *arg = (chrwork *) data;
377
378     assert(arg->map->q_input);
379     logf (LOG_DEBUG, "set qmap %.*s", (int) strlen(s), s);
380     set_map_string(arg->map->q_input, arg->map->nmem, s,
381                    strlen(s), arg->string, 0);
382     for (s = arg->string; *s; s++)
383         logf (LOG_DEBUG, " %3d", (unsigned char) *s);
384 }
385
386 static int scan_to_utf8 (iconv_t t, ucs4_t *from, size_t inlen,
387                         char *outbuf, size_t outbytesleft)
388 {
389     size_t inbytesleft = inlen * sizeof(ucs4_t);
390     char *inbuf = (char*) from;
391     size_t ret;
392    
393     if (t == (iconv_t)(-1))
394         *outbuf++ = *from;  /* ISO-8859-1 is OK here */
395     else
396     {
397         ret = iconv (t, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
398         if (ret == (size_t) (-1))
399         {
400             yaz_log (LOG_WARN|LOG_ERRNO, "bad unicode sequence");
401             return -1;
402         }
403     }
404     *outbuf = '\0';
405     return 0;
406 }
407
408 static int scan_string(char *s_native,
409                        iconv_t t_unicode, iconv_t t_utf8,
410                        void (*fun)(const char *c, void *data, int num),
411                        void *data, int *num)
412 {
413     char str[1024];
414
415     ucs4_t arg[512];
416     ucs4_t *s0, *s = arg;
417     ucs4_t c, begin, end;
418     size_t i, j;
419
420     if (t_unicode != (iconv_t)(-1))
421     {
422         char *outbuf = (char *) arg;
423         char *inbuf = s_native;
424         size_t outbytesleft = sizeof(arg)-4;
425         size_t inbytesleft = strlen(s_native);
426         size_t ret;
427         ret = iconv(t_unicode, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
428         if (ret == (size_t)(-1))
429             return -1;
430         i = (outbuf - (char*) arg)/sizeof(ucs4_t);
431     }
432     else
433     { 
434         for (i = 0; s_native[i]; i++)
435             arg[i] = s_native[i] & 255; /* ISO-8859-1 conversion */
436     }
437     arg[i] = 0;      /* terminate */
438     if (s[0] == 0xfeff || s[0] == 0xfeff)  /* skip byte Order Mark */
439         s++;
440     while (*s)
441     {
442         switch (*s)
443         {
444         case '{':
445             s++;
446             begin = zebra_prim_w(&s);
447             if (*s != '-')
448             {
449                 logf(LOG_FATAL, "Bad range in char-map");
450                 return -1;
451             }
452             s++;
453             end = zebra_prim_w(&s);
454             if (end <= begin)
455             {
456                 logf(LOG_FATAL, "Bad range in char-map");
457                 return -1;
458             }
459             s++;
460             for (c = begin; c <= end; c++)
461             {
462                 if (scan_to_utf8 (t_utf8, &c, 1, str, sizeof(str)-1))
463                     return -1;
464                 (*fun)(str, data, num ? (*num)++ : 0);
465             }
466             break;
467         case '[': s++; abort(); break;
468         case '(':
469             ++s;
470             s0 = s;
471             while (*s != ')' || s[-1] == '\\')
472                 s++;
473             *s = 0;
474             if (scan_to_utf8 (t_utf8, s0, s - s0, str, sizeof(str)-1))
475                 return -1;
476             (*fun)(str, data, num ? (*num)++ : 0);
477             s++;
478             break;
479         default:
480             c = zebra_prim_w(&s);
481             if (scan_to_utf8 (t_utf8, &c, 1, str, sizeof(str)-1))
482                 return -1;
483             (*fun)(str, data, num ? (*num)++ : 0);
484         }
485     }
486     return 0;
487 }
488
489 chrmaptab chrmaptab_create(const char *tabpath, const char *name, int map_only,
490                            const char *tabroot)
491 {
492     FILE *f;
493     char line[512], *argv[50];
494     chrmaptab res;
495     int lineno = 0;
496     int errors = 0;
497     int argc, num = (int) *CHR_BASE, i;
498     NMEM nmem;
499     iconv_t t_unicode = (iconv_t)(-1);
500     iconv_t t_utf8 = (iconv_t)(-1);
501     unsigned endian = 31;
502     const char *ucs4_native = "UCS-4";
503
504     if (*(char*) &endian == 31)      /* little endian? */
505         ucs4_native = "UCS-4LE";
506
507 #if HAVE_ICONV_H
508     t_utf8 = iconv_open ("UTF-8", ucs4_native);
509 #endif
510     logf (LOG_DEBUG, "maptab %s open", name);
511     if (!(f = yaz_fopen(tabpath, name, "r", tabroot)))
512     {
513         logf(LOG_WARN|LOG_ERRNO, "%s", name);
514         return 0;
515     }
516     nmem = nmem_create ();
517     res = (chrmaptab) nmem_malloc(nmem, sizeof(*res));
518     res->nmem = nmem;
519     res->input = (chr_t_entry *) nmem_malloc(res->nmem, sizeof(*res->input));
520     res->input->target = (unsigned char **)
521         nmem_malloc(res->nmem, sizeof(*res->input->target) * 2);
522     res->input->target[0] = (unsigned char*) CHR_UNKNOWN;
523     res->input->target[1] = 0;
524     res->input->children = (chr_t_entry **)
525         nmem_malloc(res->nmem, sizeof(res->input) * 256);
526     for (i = 0; i < 256; i++)
527     {
528         res->input->children[i] = (chr_t_entry *)
529             nmem_malloc(res->nmem, sizeof(*res->input));
530         res->input->children[i]->children = 0;
531         res->input->children[i]->target = (unsigned char **)
532             nmem_malloc (res->nmem, 2 * sizeof(unsigned char *));
533         res->input->children[i]->target[1] = 0;
534         if (map_only)
535         {
536             res->input->children[i]->target[0] = (unsigned char *)
537                 nmem_malloc (res->nmem, 2 * sizeof(unsigned char));
538             res->input->children[i]->target[0][0] = i;
539             res->input->children[i]->target[0][1] = 0;
540         }
541         else
542             res->input->children[i]->target[0] = (unsigned char*) CHR_UNKNOWN;
543     }
544     res->q_input = (chr_t_entry *)
545         nmem_malloc(res->nmem, sizeof(*res->q_input));
546     res->q_input->target = 0;
547     res->q_input->children = 0;
548
549     for (i = *CHR_BASE; i < 256; i++)
550         res->output[i] = 0;
551     res->output[(int) *CHR_SPACE] = (unsigned char *) " ";
552     res->output[(int) *CHR_UNKNOWN] = (unsigned char*) "@";
553     res->base_uppercase = 0;
554
555     while (!errors && (argc = readconf_line(f, &lineno, line, 512, argv, 50)))
556         if (!map_only && !yaz_matchstr(argv[0], "lowercase"))
557         {
558             if (argc != 2)
559             {
560                 logf(LOG_FATAL, "Syntax error in charmap");
561                 ++errors;
562             }
563             if (scan_string(argv[1], t_unicode, t_utf8, fun_addentry,
564                             res, &num) < 0)
565             {
566                 logf(LOG_FATAL, "Bad value-set specification");
567                 ++errors;
568             }
569             res->base_uppercase = num;
570             res->output[(int) *CHR_SPACE + num] = (unsigned char *) " ";
571             res->output[(int) *CHR_UNKNOWN + num] = (unsigned char*) "@";
572             num = (int) *CHR_BASE;
573         }
574         else if (!map_only && !yaz_matchstr(argv[0], "uppercase"))
575         {
576             if (!res->base_uppercase)
577             {
578                 logf(LOG_FATAL, "Uppercase directive with no lowercase set");
579                 ++errors;
580             }
581             if (argc != 2)
582             {
583                 logf(LOG_FATAL, "Missing arg for uppercase directive");
584                 ++errors;
585             }
586             if (scan_string(argv[1], t_unicode, t_utf8, fun_addentry,
587                             res, &num) < 0)
588             {
589                 logf(LOG_FATAL, "Bad value-set specification");
590                 ++errors;
591             }
592         }
593         else if (!map_only && !yaz_matchstr(argv[0], "space"))
594         {
595             if (argc != 2)
596             {
597                 logf(LOG_FATAL, "Syntax error in charmap");
598                 ++errors;
599             }
600             if (scan_string(argv[1], t_unicode, t_utf8,
601                             fun_addspace, res, 0) < 0)
602             {
603                 logf(LOG_FATAL, "Bad space specification");
604                 ++errors;
605             }
606         }
607         else if (!yaz_matchstr(argv[0], "map"))
608         {
609             chrwork buf;
610
611             if (argc != 3)
612             {
613                 logf(LOG_FATAL, "charmap directive map requires 2 args");
614                 ++errors;
615             }
616             buf.map = res;
617             buf.string[0] = '\0';
618             if (scan_string(argv[2], t_unicode, t_utf8,
619                             fun_mkstring, &buf, 0) < 0)
620             {
621                 logf(LOG_FATAL, "Bad map target");
622                 ++errors;
623             }
624             if (scan_string(argv[1], t_unicode, t_utf8,
625                             fun_add_map, &buf, 0) < 0)
626             {
627                 logf(LOG_FATAL, "Bad map source");
628                 ++errors;
629             }
630         }
631         else if (!yaz_matchstr(argv[0], "qmap"))
632         {
633             chrwork buf;
634
635             if (argc != 3)
636             {
637                 logf(LOG_FATAL, "charmap directive qmap requires 2 args");
638                 ++errors;
639             }
640             buf.map = res;
641             buf.string[0] = '\0';
642             if (scan_string(argv[2], t_unicode, t_utf8, 
643                             fun_mkstring, &buf, 0) < 0)
644             {
645                 logf(LOG_FATAL, "Bad qmap target");
646                 ++errors;
647             }
648             if (scan_string(argv[1], t_unicode, t_utf8, 
649                             fun_add_qmap, &buf, 0) < 0)
650             {
651                 logf(LOG_FATAL, "Bad qmap source");
652                 ++errors;
653             }
654         }
655         else if (!yaz_matchstr(argv[0], "encoding"))
656         {
657 #if HAVE_ICONV_H
658             if (t_unicode != (iconv_t)(-1))
659                 iconv_close (t_unicode);
660             t_unicode = iconv_open (ucs4_native, argv[1]);
661 #else
662             logf (LOG_WARN, "Encoding ignored. iconv not installed");
663 #endif
664         }
665         else
666         {
667             logf(LOG_WARN, "Syntax error at '%s' in %s", line, name);
668         }
669     
670     yaz_fclose(f);
671     if (errors)
672     {
673         chrmaptab_destroy(res);
674         res = 0;
675     }
676     logf (LOG_DEBUG, "maptab %s close %d errors", name, errors);
677 #if HAVE_ICONV_H
678     if (t_utf8 != (iconv_t)(-1))
679         iconv_close(t_utf8);
680     if (t_unicode != (iconv_t)(-1))
681         iconv_close(t_unicode);
682 #endif
683     return res;
684 }
685
686 void chrmaptab_destroy(chrmaptab tab)
687 {
688     if (tab)
689         nmem_destroy (tab->nmem);
690 }
691
692