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