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