2ce85e53700ff837ce15e7397fa2b0308d5249bb
[idzebra-moved-to-github.git] / util / charmap.c
1 /*
2  * Copyright (C) 1996-1999, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: charmap.c,v $
7  * Revision 1.17  1999-09-08 12:13:21  adam
8  * Fixed minor bug "replace"-mappings. Removed some logging messages.
9  *
10  * Revision 1.16  1999/09/07 07:19:21  adam
11  * Work on character mapping. Implemented replace rules.
12  *
13  * Revision 1.15  1999/05/26 07:49:14  adam
14  * C++ compilation.
15  *
16  * Revision 1.14  1998/10/13 20:09:18  adam
17  * Changed call to readconf_line.
18  *
19  * Revision 1.13  1997/10/27 14:33:06  adam
20  * Moved towards generic character mapping depending on "structure"
21  * field in abstract syntax file. Fixed a few memory leaks. Fixed
22  * bug with negative integers when doing searches with relational
23  * operators.
24  *
25  * Revision 1.12  1997/09/05 15:30:11  adam
26  * Changed prototype for chr_map_input - added const.
27  * Added support for C++, headers uses extern "C" for public definitions.
28  *
29  * Revision 1.11  1997/09/05 09:52:32  adam
30  * Extra argument added to function chr_read_maptab (tab path).
31  *
32  * Revision 1.10  1997/07/01 13:01:08  adam
33  * Bug fix in routine find_entry: didn't take into account the len arg.
34  *
35  * Revision 1.9  1996/10/29 13:48:14  adam
36  * Updated to use zebrautl.h instead of alexutil.h.
37  *
38  * Revision 1.8  1996/10/18 12:39:23  adam
39  * Uses LOG_DEBUG instead of LOG_WARN for "Character map overlap".
40  *
41  * Revision 1.7  1996/06/06  12:08:56  quinn
42  * Fixed bug.
43  *
44  * Revision 1.6  1996/06/04  13:28:00  quinn
45  * More work on charmapping
46  *
47  * Revision 1.5  1996/06/04  08:32:15  quinn
48  * Moved default keymap to keychars.c
49  *
50  * Revision 1.4  1996/06/03  16:32:13  quinn
51  * Temporary bug-fix
52  *
53  * Revision 1.3  1996/06/03  15:17:46  quinn
54  * Fixed bug.
55  *
56  * Revision 1.2  1996/06/03  10:15:09  quinn
57  * Fixed bug in mapping function.
58  *
59  * Revision 1.1  1996/05/31  09:07:18  quinn
60  * Work on character-set handling
61  *
62  *
63  */
64
65 /*
66  * Support module to handle character-conversions into and out of the
67  * Zebra dictionary.
68  */
69
70 #include <ctype.h>
71 #include <string.h>
72 #include <assert.h>
73
74 #include <yaz-util.h>
75 #include <charmap.h>
76
77 #define CHR_MAXSTR 1024
78 #define CHR_MAXEQUIV 32
79
80 const char *CHR_UNKNOWN = "\001";
81 const char *CHR_SPACE   = "\002";
82 const char *CHR_BASE    = "\003";
83
84 struct chrmaptab_info
85 {
86     chr_t_entry *input;         /* mapping table for input data */
87     chr_t_entry *q_input;       /* mapping table for queries */
88     unsigned char *output[256]; /* return mapping - for display of registers */
89     int base_uppercase;         /* Start of upper-case ordinals */
90     NMEM nmem;
91 };
92
93 /*
94  * Character map trie node.
95  */
96 struct chr_t_entry
97 {
98     chr_t_entry **children;  /* array of children */
99     unsigned char **target;  /* target for this node, if any */
100 };
101
102 /*
103  * General argument structure for callback functions (internal use only)
104  */
105 typedef struct chrwork 
106 {
107     chrmaptab map;
108     char string[CHR_MAXSTR+1];
109 } chrwork;
110
111 /*
112  * Add an entry to the character map.
113  */
114 static chr_t_entry *set_map_string(chr_t_entry *root, NMEM nmem,
115                                    const char *from, int len, char *to)
116 {
117     if (!root)
118     {
119         root = (chr_t_entry *) nmem_malloc(nmem, sizeof(*root));
120         root->children = 0;
121         root->target = 0;
122     }
123     if (!len)
124     {
125         if (!root->target || !root->target[0] || strcmp(root->target[0], to))
126         {
127             root->target = (unsigned char **)
128                 nmem_malloc(nmem, sizeof(*root->target)*2);
129             root->target[0] = (unsigned char *) nmem_strdup(nmem, to);
130             root->target[1] = 0;
131         }
132     }
133     else
134     {
135         if (!root->children)
136         {
137             int i;
138
139             root->children = (chr_t_entry **)
140                 nmem_malloc(nmem, sizeof(chr_t_entry*) * 256);
141             for (i = 0; i < 256; i++)
142                 root->children[i] = 0;
143         }
144         if (!(root->children[(unsigned char) *from] =
145             set_map_string(root->children[(unsigned char) *from], nmem,
146                            from + 1, len - 1, to)))
147             return 0;
148     }
149     return root;
150 }
151
152 static chr_t_entry *find_entry(chr_t_entry *t, const char **from, int len)
153 {
154     chr_t_entry *res;
155
156     if (len && t->children && t->children[(unsigned char) **from])
157     {
158         const char *pos = *from;
159
160         (*from)++;
161         if ((res = find_entry(t->children[(unsigned char) *pos],
162             from, len - 1)))
163             return res;
164         /* no match */
165         *from = pos;
166     }
167     /* no children match. use ourselves, if we have a target */
168     return t->target ? t : 0;
169 }
170
171 static chr_t_entry *find_entry_x(chr_t_entry *t, const char **from, int *len)
172 {
173     chr_t_entry *res;
174
175     while (*len <= 0)
176     {   /* switch to next buffer */
177         if (*len < 0)
178             break;
179         from++;
180         len++;
181     }
182     if (*len > 0 && t->children && t->children[(unsigned char) **from])
183     {
184         const char *old_from = *from;
185         int old_len = *len;
186         
187         (*len)--;
188         (*from)++;
189         if ((res = find_entry_x(t->children[(unsigned char) *old_from],
190                                 from, len)))
191             return res;
192         /* no match */
193         *len = old_len;
194         *from = old_from;
195     }
196     /* no children match. use ourselves, if we have a target */
197     return t->target ? t : 0;
198 }
199
200 const char **chr_map_input_x(chrmaptab maptab, const char **from, int *len)
201 {
202     chr_t_entry *t = maptab->input;
203     chr_t_entry *res;
204
205     if (!(res = find_entry_x(t, from, len)))
206         abort();
207     return (const char **) (res->target);
208 }
209
210 const char **chr_map_input(chrmaptab maptab, const char **from, int len)
211 {
212     chr_t_entry *t = maptab->input;
213     chr_t_entry *res;
214     int len_tmp[2];
215
216     len_tmp[0] = len;
217     len_tmp[1] = -1;
218     if (!(res = find_entry_x(t, from, len_tmp)))
219         abort();
220     return (const char **) (res->target);
221 }
222
223 const char *chr_map_output(chrmaptab maptab, const char **from, int len)
224 {
225     unsigned char c = ** (unsigned char **) from;
226     (*from)++;
227     return (const char*) maptab->output[c];
228 }
229
230 unsigned char zebra_prim(char **s)
231 {
232     unsigned char c;
233     unsigned int i;
234     
235     if (**s == '\\')
236     {
237         (*s)++;
238         c = **s;
239         switch (c)
240         {
241         case '\\': c = '\\'; (*s)++; break;
242         case 'r': c = '\r'; (*s)++; break;
243         case 'n': c = '\n'; (*s)++; break;
244         case 't': c = '\t'; (*s)++; break;
245         case 's': c = ' '; (*s)++; break;
246         case 'x': sscanf(*s, "x%2x", &i); c = i; *s += 3; break;
247         case '0':
248         case '1':
249         case '2':
250         case '3':
251         case '4':
252         case '5':
253         case '6':
254         case '7':
255         case '8':
256         case '9':
257             sscanf(*s, "%3o", &i);
258             c = i;
259             *s += 3;
260             break;
261         default:
262             (*s)++;
263         }
264         return c;
265     }
266     c = **s;
267     ++(*s);
268     return c;
269 }
270
271 /*
272  * Callback function.
273  * Add an entry to the value space.
274  */
275 static void fun_addentry(const char *s, void *data, int num)
276 {
277     chrmaptab tab = (chrmaptab) data;
278     char tmp[2];
279     
280     tmp[0] = num; tmp[1] = '\0';
281     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s), tmp);
282     tab->output[num + tab->base_uppercase] =
283         (unsigned char *) nmem_strdup(tab->nmem, s);
284 }
285
286 /* 
287  * Callback function.
288  * Add a space-entry to the value space.
289  */
290 static void fun_addspace(const char *s, void *data, int num)
291 {
292     chrmaptab tab = (chrmaptab) data;
293     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
294                                 (char*) CHR_SPACE);
295 }
296
297 /*
298  * Create a string containing the mapped characters provided.
299  */
300 static void fun_mkstring(const char *s, void *data, int num)
301 {
302     chrwork *arg = (chrwork *) data;
303     const char **res, *p = s;
304
305     res = chr_map_input(arg->map, &s, strlen(s));
306     if (*res == (char*) CHR_UNKNOWN)
307         logf(LOG_WARN, "Map: '%s' has no mapping", p);
308     strncat(arg->string, *res, CHR_MAXSTR - strlen(arg->string));
309     arg->string[CHR_MAXSTR] = '\0';
310 }
311
312 /*
313  * Add a map to the string contained in the argument.
314  */
315 static void fun_add_map(const char *s, void *data, int num)
316 {
317     chrwork *arg = (chrwork *) data;
318
319     assert(arg->map->input);
320     logf (LOG_DEBUG, "set map %.*s", (int) strlen(s), s);
321     set_map_string(arg->map->input, arg->map->nmem, s, strlen(s), arg->string);
322     for (s = arg->string; *s; s++)
323         logf (LOG_DEBUG, " %3d", (unsigned char) *s);
324 }
325
326 /*
327  * Add a query map to the string contained in the argument.
328  */
329 static void fun_add_qmap(const char *s, void *data, int num)
330 {
331     chrwork *arg = (chrwork *) data;
332
333     assert(arg->map->q_input);
334     logf (LOG_DEBUG, "set qmap %.*s", (int) strlen(s), s);
335     set_map_string(arg->map->q_input, arg->map->nmem, s,
336                    strlen(s), arg->string);
337     for (s = arg->string; *s; s++)
338         logf (LOG_DEBUG, " %3d", (unsigned char) *s);
339 }
340
341
342 static int scan_string(char *s,
343                        void (*fun)(const char *c, void *data, int num),
344                        void *data, int *num)
345 {
346     unsigned char c, str[1024], begin, end, *p;
347     
348     while (*s)
349     {
350         switch (*s)
351         {
352         case '{':
353             s++;
354             begin = zebra_prim(&s);
355             if (*s != '-')
356             {
357                 logf(LOG_FATAL, "Bad range in char-map");
358                 return -1;
359             }
360             s++;
361             end = zebra_prim(&s);
362             if (end <= begin)
363             {
364                 logf(LOG_FATAL, "Bad range in char-map");
365                 return -1;
366             }
367             s++;
368             for (c = begin; c <= end; c++)
369             {
370                 str[0] = c; str[1] = '\0';
371                 (*fun)((char *) str, data, num ? (*num)++ : 0);
372             }
373             break;
374         case '[': s++; abort(); break;
375         case '(':
376             p = (unsigned char*) ++s;
377                 /* Find the end-marker, ignoring escapes */
378             do
379             {
380                 if (!(p = (unsigned char*) strchr((char*) p, ')')))
381                 {
382                     logf(LOG_FATAL, "Missing ')' in string");
383                     return -1;
384                 }
385             }
386             while (*(p -  1) == '\\');
387             *p = 0;
388             (*fun)(s, data, num ? (*num)++ : 0);
389             s = (char*) p + 1;
390             break;
391         default:
392             c = zebra_prim(&s);
393             str[0] = c; str[1] = '\0';
394             (*fun)((char *) str, data, num ? (*num)++ : 0);
395         }
396     }
397     return 0;
398 }
399
400 chrmaptab chrmaptab_create(const char *tabpath, const char *name, int map_only)
401 {
402     FILE *f;
403     char line[512], *argv[50];
404     chrmaptab res;
405     int lineno = 0;
406     int errors = 0;
407     int argc, num = (int) *CHR_BASE, i;
408     NMEM nmem;
409
410     logf (LOG_DEBUG, "maptab %s open", name);
411     if (!(f = yaz_path_fopen(tabpath, name, "r")))
412     {
413         logf(LOG_WARN|LOG_ERRNO, "%s", name);
414         return 0;
415     }
416     nmem = nmem_create ();
417     res = (chrmaptab) nmem_malloc(nmem, sizeof(*res));
418     res->nmem = nmem;
419     res->input = (chr_t_entry *) nmem_malloc(res->nmem, sizeof(*res->input));
420     res->input->target = (unsigned char **)
421         nmem_malloc(res->nmem, sizeof(*res->input->target) * 2);
422     res->input->target[0] = (unsigned char*) CHR_UNKNOWN;
423     res->input->target[1] = 0;
424     res->input->children = (chr_t_entry **)
425         nmem_malloc(res->nmem, sizeof(res->input) * 256);
426     for (i = 0; i < 256; i++)
427     {
428         res->input->children[i] = (chr_t_entry *)
429             nmem_malloc(res->nmem, sizeof(*res->input));
430         res->input->children[i]->children = 0;
431         res->input->children[i]->target = (unsigned char **)
432             nmem_malloc (res->nmem, 2 * sizeof(unsigned char *));
433         res->input->children[i]->target[1] = 0;
434         if (map_only)
435         {
436             res->input->children[i]->target[0] = (unsigned char *)
437                 nmem_malloc (res->nmem, 2 * sizeof(unsigned char));
438             res->input->children[i]->target[0][0] = i;
439             res->input->children[i]->target[0][1] = 0;
440         }
441         else
442             res->input->children[i]->target[0] = (unsigned char*) CHR_UNKNOWN;
443     }
444     res->q_input = (chr_t_entry *)
445         nmem_malloc(res->nmem, sizeof(*res->q_input));
446     res->q_input->target = 0;
447     res->q_input->children = 0;
448
449     for (i = *CHR_BASE; i < 256; i++)
450         res->output[i] = 0;
451     res->output[(int) *CHR_SPACE] = (unsigned char *) " ";
452     res->output[(int) *CHR_UNKNOWN] = (unsigned char*) "@";
453     res->base_uppercase = 0;
454
455     while (!errors && (argc = readconf_line(f, &lineno, line, 512, argv, 50)))
456         if (!map_only && !yaz_matchstr(argv[0], "lowercase"))
457         {
458             if (argc != 2)
459             {
460                 logf(LOG_FATAL, "Syntax error in charmap");
461                 ++errors;
462             }
463             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
464             {
465                 logf(LOG_FATAL, "Bad value-set specification");
466                 ++errors;
467             }
468             res->base_uppercase = num;
469             res->output[(int) *CHR_SPACE + num] = (unsigned char *) " ";
470             res->output[(int) *CHR_UNKNOWN + num] = (unsigned char*) "@";
471             num = (int) *CHR_BASE;
472         }
473         else if (!map_only && !yaz_matchstr(argv[0], "uppercase"))
474         {
475             if (!res->base_uppercase)
476             {
477                 logf(LOG_FATAL, "Uppercase directive with no lowercase set");
478                 ++errors;
479             }
480             if (argc != 2)
481             {
482                 logf(LOG_FATAL, "Missing arg for uppercase directive");
483                 ++errors;
484             }
485             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
486             {
487                 logf(LOG_FATAL, "Bad value-set specification");
488                 ++errors;
489             }
490         }
491         else if (!map_only && !yaz_matchstr(argv[0], "space"))
492         {
493             if (argc != 2)
494             {
495                 logf(LOG_FATAL, "Syntax error in charmap");
496                 ++errors;
497             }
498             if (scan_string(argv[1], fun_addspace, res, 0) < 0)
499             {
500                 logf(LOG_FATAL, "Bad space specification");
501                 ++errors;
502             }
503         }
504         else if (!yaz_matchstr(argv[0], "map"))
505         {
506             chrwork buf;
507
508             if (argc != 3)
509             {
510                 logf(LOG_FATAL, "charmap directive map requires 2 args");
511                 ++errors;
512             }
513             buf.map = res;
514             buf.string[0] = '\0';
515             if (scan_string(argv[2], fun_mkstring, &buf, 0) < 0)
516             {
517                 logf(LOG_FATAL, "Bad map target");
518                 ++errors;
519             }
520             if (scan_string(argv[1], fun_add_map, &buf, 0) < 0)
521             {
522                 logf(LOG_FATAL, "Bad map source");
523                 ++errors;
524             }
525         }
526         else if (!yaz_matchstr(argv[0], "qmap"))
527         {
528             chrwork buf;
529
530             if (argc != 3)
531             {
532                 logf(LOG_FATAL, "charmap directive qmap requires 2 args");
533                 ++errors;
534             }
535             buf.map = res;
536             buf.string[0] = '\0';
537             if (scan_string(argv[2], fun_mkstring, &buf, 0) < 0)
538             {
539                 logf(LOG_FATAL, "Bad qmap target");
540                 ++errors;
541             }
542             if (scan_string(argv[1], fun_add_qmap, &buf, 0) < 0)
543             {
544                 logf(LOG_FATAL, "Bad qmap source");
545                 ++errors;
546             }
547         }
548         else
549         {
550             logf(LOG_WARN, "Syntax error at '%s' in %s", line, name);
551         }
552     
553     fclose(f);
554     if (errors)
555     {
556         chrmaptab_destroy(res);
557         res = 0;
558     }
559     logf (LOG_DEBUG, "maptab %s close %d errors", name, errors);
560     return res;
561 }
562
563 void chrmaptab_destroy(chrmaptab tab)
564 {
565     if (tab)
566         nmem_destroy (tab->nmem);
567 }
568
569