Moved towards generic character mapping depending on "structure"
[idzebra-moved-to-github.git] / util / charmap.c
1 /*
2  * Copyright (C) 1996-1997, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: charmap.c,v $
7  * Revision 1.13  1997-10-27 14:33:06  adam
8  * Moved towards generic character mapping depending on "structure"
9  * field in abstract syntax file. Fixed a few memory leaks. Fixed
10  * bug with negative integers when doing searches with relational
11  * operators.
12  *
13  * Revision 1.12  1997/09/05 15:30:11  adam
14  * Changed prototype for chr_map_input - added const.
15  * Added support for C++, headers uses extern "C" for public definitions.
16  *
17  * Revision 1.11  1997/09/05 09:52:32  adam
18  * Extra argument added to function chr_read_maptab (tab path).
19  *
20  * Revision 1.10  1997/07/01 13:01:08  adam
21  * Bug fix in routine find_entry: didn't take into account the len arg.
22  *
23  * Revision 1.9  1996/10/29 13:48:14  adam
24  * Updated to use zebrautl.h instead of alexutil.h.
25  *
26  * Revision 1.8  1996/10/18 12:39:23  adam
27  * Uses LOG_DEBUG instead of LOG_WARN for "Character map overlap".
28  *
29  * Revision 1.7  1996/06/06  12:08:56  quinn
30  * Fixed bug.
31  *
32  * Revision 1.6  1996/06/04  13:28:00  quinn
33  * More work on charmapping
34  *
35  * Revision 1.5  1996/06/04  08:32:15  quinn
36  * Moved default keymap to keychars.c
37  *
38  * Revision 1.4  1996/06/03  16:32:13  quinn
39  * Temporary bug-fix
40  *
41  * Revision 1.3  1996/06/03  15:17:46  quinn
42  * Fixed bug.
43  *
44  * Revision 1.2  1996/06/03  10:15:09  quinn
45  * Fixed bug in mapping function.
46  *
47  * Revision 1.1  1996/05/31  09:07:18  quinn
48  * Work on character-set handling
49  *
50  *
51  */
52
53 /*
54  * Support module to handle character-conversions into and out of the
55  * Zebra dictionary.
56  */
57
58 #include <ctype.h>
59 #include <string.h>
60 #include <assert.h>
61
62 #include <yaz-util.h>
63 #include <charmap.h>
64
65 #define CHR_MAXSTR 1024
66 #define CHR_MAXEQUIV 32
67
68 int chr_map_chrs(chr_t_entry *t, char **from, int len,
69                  int *read, char **to, int max);
70
71 const char *CHR_UNKNOWN = "\001";
72 const char *CHR_SPACE   = "\002";
73 const char *CHR_BASE    = "\003";
74
75 struct chrmaptab_info
76 {
77     chr_t_entry *input;         /* mapping table for input data */
78     chr_t_entry *query_equiv;   /* mapping table for queries */
79     unsigned char *output[256]; /* return mapping - for display of registers */
80     int base_uppercase;         /* Start of upper-case ordinals */
81     char **tmp_buf;
82     NMEM nmem;
83 };
84
85 /*
86  * Character map trie node.
87  */
88 struct chr_t_entry
89 {
90     chr_t_entry **children; /* array of children */
91     unsigned char *target;  /* target for this node, if any */
92     unsigned char *equiv;   /* equivalent to, or sumthin */
93 };
94
95 /*
96  * General argument structure for callback functions (internal use only)
97  */
98 typedef struct chrwork 
99 {
100     chrmaptab map;
101     char string[CHR_MAXSTR+1];
102 } chrwork;
103
104 /*
105  * Add an entry to the character map.
106  */
107 static chr_t_entry *set_map_string(chr_t_entry *root, NMEM nmem,
108                                    const char *from, int len, char *to)
109 {
110     if (!root)
111     {
112         root = nmem_malloc(nmem, sizeof(*root));
113         root->children = 0;
114         root->target = 0;
115     }
116     if (!len)
117     {
118         if (!root->target || (char*) root->target == CHR_SPACE ||
119             (char*) root->target == CHR_UNKNOWN)
120             root->target = (unsigned char *) nmem_strdup(nmem, to);
121         else if ((char*) to != CHR_SPACE)
122             logf(LOG_DEBUG, "Character map overlap");
123     }
124     else
125     {
126         if (!root->children)
127         {
128             int i;
129
130             root->children = nmem_malloc(nmem, sizeof(chr_t_entry*) * 256);
131             for (i = 0; i < 256; i++)
132                 root->children[i] = 0;
133         }
134         if (!(root->children[(unsigned char) *from] =
135             set_map_string(root->children[(unsigned char) *from], nmem,
136                            from + 1, len - 1, to)))
137             return 0;
138     }
139     return root;
140 }
141
142 int chr_map_chrs(chr_t_entry *t, char **from, int len, int *read, char **to,
143     int max)
144 {
145     int i = 0;
146     unsigned char *s;
147
148     while (len && t->children && t->children[(unsigned char) **from])
149     {
150         t = t->children[(unsigned char) **from];
151         (*from)++;
152         len--;
153     }
154     /* if there were no matches, we are still at the root node,
155        which always has a null mapping */
156     for (s = t->target; *s && max; s++)
157     {
158         **to = *s;
159         s++;
160         (*to)++;
161         max--;
162         i++;
163     }
164     return i;
165 }
166
167
168 static chr_t_entry *find_entry(chr_t_entry *t, const char **from, int len)
169 {
170     chr_t_entry *res;
171
172     if (len && t->children && t->children[(unsigned char) **from])
173     {
174         const char *pos = *from;
175
176         (*from)++;
177         if ((res = find_entry(t->children[(unsigned char) *pos],
178             from, len - 1)))
179             return res;
180         /* no match */
181         *from = pos;
182     }
183     /* no children match. use ourselves, if we have a target */
184    return t->target ? t : 0;
185 }
186
187 const char **chr_map_input(chrmaptab maptab, const char **from, int len)
188 {
189     chr_t_entry *t = maptab->input;
190     chr_t_entry *res;
191
192     if (!(res = find_entry(t, from, len)))
193         abort();
194     maptab->tmp_buf[0] = (char*) res->target;
195     maptab->tmp_buf[1] = NULL;
196     return (const char **) maptab->tmp_buf;
197 }
198
199 const char *chr_map_output(chrmaptab maptab, const char **from, int len)
200 {
201     unsigned char c = ** (unsigned char **) from;
202     (*from)++;
203     return (const char*) maptab->output[c];
204 }
205
206 static unsigned char prim(char **s)
207 {
208     unsigned char c;
209     unsigned int i;
210
211     if (**s == '\\')
212     {
213         (*s)++;
214         c = **s;
215         switch (c)
216         {
217             case '\\': c = '\\'; (*s)++; break;
218             case 'r': c = '\r'; (*s)++; break;
219             case 'n': c = '\n'; (*s)++; break;
220             case 't': c = '\t'; (*s)++; break;
221             case 's': c = ' '; (*s)++; break;
222             case 'x': sscanf(*s, "x%2x", &i); c = i; *s += 3; break;
223             case '{': case '[': case '(': case '}': case ']': case ')':
224                 (*s)++;
225                 break;
226             default: sscanf(*s, "%3o", &i); c = i; *s += 3; break;
227         }
228         return c;
229     }
230     c = **s;
231     ++(*s);
232     return c;
233 }
234
235 /*
236  * Callback function.
237  * Add an entry to the value space.
238  */
239 static void fun_addentry(const char *s, void *data, int num)
240 {
241     chrmaptab tab = data;
242     char tmp[2];
243
244     tmp[0] = num; tmp[1] = '\0';
245     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s), tmp);
246     tab->output[num + tab->base_uppercase] =
247         (unsigned char *) nmem_strdup(tab->nmem, s);
248 }
249
250 /* 
251  * Callback function.
252  * Add a space-entry to the value space.
253  */
254 static void fun_addspace(const char *s, void *data, int num)
255 {
256     chrmaptab tab = data;
257     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
258                                 (char*) CHR_SPACE);
259 }
260
261 /*
262  * Create a string containing the mapped characters provided.
263  */
264 static void fun_mkstring(const char *s, void *data, int num)
265 {
266     chrwork *arg = data;
267     const char **res, *p = s;
268
269     res = chr_map_input(arg->map, &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(const char *s, void *data, int num)
280 {
281     chrwork *arg = data;
282
283     assert(arg->map->input);
284     set_map_string(arg->map->input, arg->map->nmem, s, strlen(s), arg->string);
285 }
286
287 static int scan_string(char *s, void (*fun)(const 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 chrmaptab_create(const char *tabpath, const char *name, int map_only)
345 {
346     FILE *f;
347     char line[512], *argv[50];
348     chrmaptab res;
349     int argc, num = (int) *CHR_BASE, i;
350
351     if (!(f = yaz_path_fopen(tabpath, name, "r")))
352     {
353         logf(LOG_WARN|LOG_ERRNO, "%s", name);
354         return 0;
355     }
356     res = xmalloc(sizeof(*res));
357     res->nmem = nmem_create ();
358     res->tmp_buf = nmem_malloc (res->nmem, sizeof(*res->tmp_buf) * 100);
359     res->input = nmem_malloc(res->nmem, sizeof(*res->input));
360     res->input->target = (unsigned char*) CHR_UNKNOWN;
361     res->input->equiv = 0;
362     res->input->children = nmem_malloc(res->nmem, sizeof(res->input) * 256);
363     for (i = 0; i < 256; i++)
364     {
365         res->input->children[i] = nmem_malloc(res->nmem, sizeof(*res->input));
366         res->input->children[i]->children = 0;
367         if (map_only)
368         {
369             res->input->children[i]->target = nmem_malloc (res->nmem,
370                                                            2 * sizeof(char));
371             res->input->children[i]->target[0] = i;
372             res->input->children[i]->target[1] = 0;
373         }
374         else
375             res->input->children[i]->target = (unsigned char*) CHR_UNKNOWN;
376         res->input->children[i]->equiv = 0;
377     }
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 (!map_only && !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 (!map_only && !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 (!map_only && !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             logf(LOG_WARN, "Syntax error at '%s' in %s", line, name);
469         }
470     fclose(f);
471     return res;
472 }
473
474 void chrmaptab_destroy(chrmaptab tab)
475 {
476     nmem_destroy (tab->nmem);
477     xfree (tab);
478 }
479
480