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