Updated to use zebrautl.h instead of alexutil.h.
[idzebra-moved-to-github.git] / util / charmap.c
1 /*
2  * Copyright (C) 1994, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: charmap.c,v $
7  * Revision 1.9  1996-10-29 13:48:14  adam
8  * Updated to use zebrautl.h instead of alexutil.h.
9  *
10  * Revision 1.8  1996/10/18 12:39:23  adam
11  * Uses LOG_DEBUG instead of LOG_WARN for "Character map overlap".
12  *
13  * Revision 1.7  1996/06/06  12:08:56  quinn
14  * Fixed bug.
15  *
16  * Revision 1.6  1996/06/04  13:28:00  quinn
17  * More work on charmapping
18  *
19  * Revision 1.5  1996/06/04  08:32:15  quinn
20  * Moved default keymap to keychars.c
21  *
22  * Revision 1.4  1996/06/03  16:32:13  quinn
23  * Temporary bug-fix
24  *
25  * Revision 1.3  1996/06/03  15:17:46  quinn
26  * Fixed bug.
27  *
28  * Revision 1.2  1996/06/03  10:15:09  quinn
29  * Fixed bug in mapping function.
30  *
31  * Revision 1.1  1996/05/31  09:07:18  quinn
32  * Work on character-set handling
33  *
34  *
35  */
36
37 /*
38  * Support module to handle character-conversions into and out of the
39  * Zebra dictionary.
40  */
41
42 #include <ctype.h>
43 #include <string.h>
44 #include <assert.h>
45
46 #include <zebrautl.h>
47 #include <yaz-util.h>
48 #include <charmap.h>
49 #include <tpath.h>
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 extern char *data1_tabpath;
59
60 /*
61  * Character map trie node.
62  */
63 struct chr_t_entry
64 {
65     chr_t_entry **children; /* array of children */
66     unsigned char *target;  /* target for this node, if any */
67     unsigned char *equiv;   /* equivalent to, or sumthin */
68 };
69
70 /*
71  * General argument structure for callback functions (internal use only)
72  */
73 typedef struct chrwork 
74 {
75     chrmaptab *map;
76     char string[CHR_MAXSTR+1];
77 } chrwork;
78
79 /*
80  * Add an entry to the character map.
81  */
82 static chr_t_entry *set_map_string(chr_t_entry *root, char *from, int len,
83     char *to)
84 {
85     if (!root)
86     {
87         root = xmalloc(sizeof(*root));
88         root->children = 0;
89         root->target = 0;
90     }
91     if (!len)
92     {
93         if (!root->target || (char*) root->target == CHR_SPACE ||
94             (char*) root->target == CHR_UNKNOWN)
95             root->target = (unsigned char *) xstrdup(to);
96         else if ((char*) to != CHR_SPACE)
97             logf(LOG_DEBUG, "Character map overlap");
98     }
99     else
100     {
101         if (!root->children)
102         {
103             int i;
104
105             root->children = xmalloc(sizeof(chr_t_entry*) * 256);
106             for (i = 0; i < 256; i++)
107                 root->children[i] = 0;
108         }
109         if (!(root->children[(unsigned char) *from] =
110             set_map_string(root->children[(unsigned char) *from], from + 1,
111             len - 1, to)))
112             return 0;
113     }
114     return root;
115 }
116
117 int chr_map_chrs(chr_t_entry *t, char **from, int len, int *read, char **to,
118     int max)
119 {
120     int i = 0;
121     unsigned char *s;
122
123     while (len && t->children && t->children[(unsigned char) **from])
124     {
125         t = t->children[(unsigned char) **from];
126         (*from)++;
127         len--;
128     }
129     /* if there were no matches, we are still at the root node,
130        which always has a null mapping */
131     for (s = t->target; *s && max; s++)
132     {
133         **to = *s;
134         s++;
135         (*to)++;
136         max--;
137         i++;
138     }
139     return i;
140 }
141
142 #if 1
143
144 static chr_t_entry *find_entry(chr_t_entry *t, char **from, int len)
145 {
146     chr_t_entry *res;
147
148     if (t->children && t->children[(unsigned char) **from])
149     {
150         char *pos = *from;
151
152         (*from)++;
153         if ((res = find_entry(t->children[(unsigned char) *pos],
154             from, len - 1)))
155             return res;
156         /* no match */
157         *from = pos;
158     }
159     /* no children match. use ourselves, if we have a target */
160    return t->target ? t : 0;
161 }
162
163 char **chr_map_input(chr_t_entry *t, char **from, int len)
164 {
165     static char *buf[2] = {0, 0};
166     chr_t_entry *res;
167
168     if (!(res = find_entry(t, from, len)))
169         abort();
170     buf[0] = (char *) res->target;
171     return buf;
172 }
173
174 #else
175
176 char **chr_map_input(chr_t_entry *t, char **from, int len)
177 {
178     static char *buf[2] = {0, 0}, str[2] = {0, 0};
179     char *start = *from;
180
181     if (t)
182     {
183         while (len && t->children && t->children[(unsigned char) **from])
184         {
185             t = t->children[(unsigned char) **from];
186             (*from)++;
187             len--;
188         }
189         buf[0] = (char*) t->target;
190     }
191     else /* null mapping */
192     {
193         if (isalnum(**from))
194         {
195             str[0] = **from;
196             buf[0] = str;
197         }
198         else if (isspace(**from))
199             buf[0] = (char*) CHR_SPACE;
200         else
201             buf[0] = (char*) CHR_UNKNOWN;
202     }
203     if (start == *from)
204         (*from)++;
205     return buf;
206     /* return (char*) t->target; */
207 }
208
209 #endif
210
211 static unsigned char prim(char **s)
212 {
213     unsigned char c;
214     unsigned int i;
215
216     if (**s == '\\')
217     {
218         (*s)++;
219         c = **s;
220         switch (c)
221         {
222             case '\\': c = '\\'; (*s)++; break;
223             case 'r': c = '\r'; (*s)++; break;
224             case 'n': c = '\n'; (*s)++; break;
225             case 't': c = '\t'; (*s)++; break;
226             case 's': c = ' '; (*s)++; break;
227             case 'x': sscanf(*s, "x%2x", &i); c = i; *s += 3; break;
228             case '{': case '[': case '(': case '}': case ']': case ')':
229                 (*s)++;
230                 break;
231             default: sscanf(*s, "%3o", &i); c = i; *s += 3; break;
232         }
233         return c;
234     }
235     c = **s;
236     ++(*s);
237     return c;
238 }
239
240 /*
241  * Callback function.
242  * Add an entry to the value space.
243  */
244 static void fun_addentry(char *s, void *data, int num)
245 {
246     chrmaptab *tab = data;
247     char tmp[2];
248
249     tmp[0] = num; tmp[1] = '\0';
250     tab->input = set_map_string(tab->input, s, strlen(s), tmp);
251     tab->output[num + tab->base_uppercase] = (unsigned char *) xstrdup(s);
252 }
253
254 /* 
255  * Callback function.
256  * Add a space-entry to the value space.
257  */
258 static void fun_addspace(char *s, void *data, int num)
259 {
260     chrmaptab *tab = data;
261     tab->input = set_map_string(tab->input, s, strlen(s), (char*) CHR_SPACE);
262 }
263
264 /*
265  * Create a string containing the mapped characters provided.
266  */
267 static void fun_mkstring(char *s, void *data, int num)
268 {
269     chrwork *arg = data;
270     char **res, *p = s;
271
272     res = chr_map_input(arg->map->input, &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(char *s, void *data, int num)
283 {
284     chrwork *arg = data;
285
286     assert(arg->map->input);
287     set_map_string(arg->map->input, s, strlen(s), arg->string);
288 }
289
290 static int scan_string(char *s, void (*fun)(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 *chr_read_maptab(char *name)
348 {
349     FILE *f;
350     char line[512], *argv[50];
351     chrmaptab *res = xmalloc(sizeof(*res));
352     int argc, num = (int) *CHR_BASE, i;
353
354     if (!(f = yaz_path_fopen(data1_tabpath, name, "r")))
355     {
356         logf(LOG_WARN|LOG_ERRNO, "%s", name);
357         return 0;
358     }
359     res = xmalloc(sizeof(*res));
360     res->input = xmalloc(sizeof(*res->input));
361     res->input->target = (unsigned char*) CHR_UNKNOWN;
362     res->input->equiv = 0;
363 #if 1
364     res->input->children = xmalloc(sizeof(res->input) * 256);
365     for (i = 0; i < 256; i++)
366     {
367         res->input->children[i] = xmalloc(sizeof(*res->input));
368         res->input->children[i]->children = 0;
369         res->input->children[i]->target = (unsigned char*) CHR_UNKNOWN;
370         res->input->children[i]->equiv = 0;
371     }
372 #else
373     res->input->children = 0;
374 #endif
375     res->query_equiv = 0;
376     for (i = *CHR_BASE; i < 256; i++)
377         res->output[i] = 0;
378     res->output[(int) *CHR_SPACE] = (unsigned char *) " ";
379     res->output[(int) *CHR_UNKNOWN] = (unsigned char*) "@";
380     res->base_uppercase = 0;
381
382     while ((argc = readconf_line(f, line, 512, argv, 50)))
383         if (!yaz_matchstr(argv[0], "lowercase"))
384         {
385             if (argc != 2)
386             {
387                 logf(LOG_FATAL, "Syntax error in charmap");
388                 fclose(f);
389                 return 0;
390             }
391             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
392             {
393                 logf(LOG_FATAL, "Bad value-set specification");
394                 fclose(f);
395                 return 0;
396             }
397             res->base_uppercase = num;
398             res->output[(int) *CHR_SPACE + num] = (unsigned char *) " ";
399             res->output[(int) *CHR_UNKNOWN + num] = (unsigned char*) "@";
400             num = (int) *CHR_BASE;
401         }
402         else if (!yaz_matchstr(argv[0], "uppercase"))
403         {
404             if (!res->base_uppercase)
405             {
406                 logf(LOG_FATAL, "Uppercase directive with no lowercase set");
407                 fclose(f);
408                 return 0;
409             }
410             if (argc != 2)
411             {
412                 logf(LOG_FATAL, "Syntax error in charmap");
413                 fclose(f);
414                 return 0;
415             }
416             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
417             {
418                 logf(LOG_FATAL, "Bad value-set specification");
419                 fclose(f);
420                 return 0;
421             }
422         }
423         else if (!yaz_matchstr(argv[0], "space"))
424         {
425             if (argc != 2)
426             {
427                 logf(LOG_FATAL, "Syntax error in charmap");
428                 fclose(f);
429                 return 0;
430             }
431             if (scan_string(argv[1], fun_addspace, res, 0) < 0)
432             {
433                 logf(LOG_FATAL, "Bad space specification");
434                 fclose(f);
435                 return 0;
436             }
437         }
438         else if (!yaz_matchstr(argv[0], "map"))
439         {
440             chrwork buf;
441
442             if (argc != 3)
443             {
444                 logf(LOG_FATAL, "charmap MAP directive requires 2 args");
445                 fclose(f);
446                 return 0;
447             }
448             buf.map = res;
449             buf.string[0] = '\0';
450             if (scan_string(argv[2], fun_mkstring, &buf, 0) < 0)
451             {
452                 logf(LOG_FATAL, "Bad map target");
453                 fclose(f);
454                 return 0;
455             }
456             if (scan_string(argv[1], fun_addmap, &buf, 0) < 0)
457             {
458                 logf(LOG_FATAL, "Bad map source");
459                 fclose(f);
460                 return 0;
461             }
462         }
463         else
464         {
465 #if 0
466             logf(LOG_WARN, "Syntax error at '%s' in %s", line, file);
467             fclose(f);
468             return 0;
469 #endif
470         }
471     fclose(f);
472     return res;
473 }