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