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