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