Work on character mapping. Implemented replace rules.
[idzebra-moved-to-github.git] / util / charmap.c
1 /*
2  * Copyright (C) 1996-1999, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: charmap.c,v $
7  * Revision 1.16  1999-09-07 07:19:21  adam
8  * Work on character mapping. Implemented replace rules.
9  *
10  * Revision 1.15  1999/05/26 07:49:14  adam
11  * C++ compilation.
12  *
13  * Revision 1.14  1998/10/13 20:09:18  adam
14  * Changed call to readconf_line.
15  *
16  * Revision 1.13  1997/10/27 14:33:06  adam
17  * Moved towards generic character mapping depending on "structure"
18  * field in abstract syntax file. Fixed a few memory leaks. Fixed
19  * bug with negative integers when doing searches with relational
20  * operators.
21  *
22  * Revision 1.12  1997/09/05 15:30:11  adam
23  * Changed prototype for chr_map_input - added const.
24  * Added support for C++, headers uses extern "C" for public definitions.
25  *
26  * Revision 1.11  1997/09/05 09:52:32  adam
27  * Extra argument added to function chr_read_maptab (tab path).
28  *
29  * Revision 1.10  1997/07/01 13:01:08  adam
30  * Bug fix in routine find_entry: didn't take into account the len arg.
31  *
32  * Revision 1.9  1996/10/29 13:48:14  adam
33  * Updated to use zebrautl.h instead of alexutil.h.
34  *
35  * Revision 1.8  1996/10/18 12:39:23  adam
36  * Uses LOG_DEBUG instead of LOG_WARN for "Character map overlap".
37  *
38  * Revision 1.7  1996/06/06  12:08:56  quinn
39  * Fixed bug.
40  *
41  * Revision 1.6  1996/06/04  13:28:00  quinn
42  * More work on charmapping
43  *
44  * Revision 1.5  1996/06/04  08:32:15  quinn
45  * Moved default keymap to keychars.c
46  *
47  * Revision 1.4  1996/06/03  16:32:13  quinn
48  * Temporary bug-fix
49  *
50  * Revision 1.3  1996/06/03  15:17:46  quinn
51  * Fixed bug.
52  *
53  * Revision 1.2  1996/06/03  10:15:09  quinn
54  * Fixed bug in mapping function.
55  *
56  * Revision 1.1  1996/05/31  09:07:18  quinn
57  * Work on character-set handling
58  *
59  *
60  */
61
62 /*
63  * Support module to handle character-conversions into and out of the
64  * Zebra dictionary.
65  */
66
67 #include <ctype.h>
68 #include <string.h>
69 #include <assert.h>
70
71 #include <yaz-util.h>
72 #include <charmap.h>
73
74 #define CHR_MAXSTR 1024
75 #define CHR_MAXEQUIV 32
76
77 const char *CHR_UNKNOWN = "\001";
78 const char *CHR_SPACE   = "\002";
79 const char *CHR_BASE    = "\003";
80
81 struct chrmaptab_info
82 {
83     chr_t_entry *input;         /* mapping table for input data */
84     chr_t_entry *q_input;       /* mapping table for queries */
85     unsigned char *output[256]; /* return mapping - for display of registers */
86     int base_uppercase;         /* Start of upper-case ordinals */
87     NMEM nmem;
88 };
89
90 /*
91  * Character map trie node.
92  */
93 struct chr_t_entry
94 {
95     chr_t_entry **children;  /* array of children */
96     unsigned char **target;  /* target for this node, if any */
97 };
98
99 /*
100  * General argument structure for callback functions (internal use only)
101  */
102 typedef struct chrwork 
103 {
104     chrmaptab map;
105     char string[CHR_MAXSTR+1];
106 } chrwork;
107
108 /*
109  * Add an entry to the character map.
110  */
111 static chr_t_entry *set_map_string(chr_t_entry *root, NMEM nmem,
112                                    const char *from, int len, char *to)
113 {
114     if (!root)
115     {
116         root = (chr_t_entry *) nmem_malloc(nmem, sizeof(*root));
117         root->children = 0;
118         root->target = 0;
119     }
120     if (!len)
121     {
122         if (!root->target || !root->target[0] || strcmp(root->target[0], to))
123         {
124             root->target = (unsigned char **)
125                 nmem_malloc(nmem, sizeof(*root->target)*2);
126             root->target[0] = (unsigned char *) nmem_strdup(nmem, to);
127             root->target[1] = 0;
128         }
129     }
130     else
131     {
132         if (!root->children)
133         {
134             int i;
135
136             root->children = (chr_t_entry **)
137                 nmem_malloc(nmem, sizeof(chr_t_entry*) * 256);
138             for (i = 0; i < 256; i++)
139                 root->children[i] = 0;
140         }
141         if (!(root->children[(unsigned char) *from] =
142             set_map_string(root->children[(unsigned char) *from], nmem,
143                            from + 1, len - 1, to)))
144             return 0;
145     }
146     return root;
147 }
148
149 static chr_t_entry *find_entry(chr_t_entry *t, const char **from, int len)
150 {
151     chr_t_entry *res;
152
153     if (len && t->children && t->children[(unsigned char) **from])
154     {
155         const char *pos = *from;
156
157         (*from)++;
158         if ((res = find_entry(t->children[(unsigned char) *pos],
159             from, len - 1)))
160             return res;
161         /* no match */
162         *from = pos;
163     }
164     /* no children match. use ourselves, if we have a target */
165     return t->target ? t : 0;
166 }
167
168 static chr_t_entry *find_entry_x(chr_t_entry *t, const char **from, int *len)
169 {
170     chr_t_entry *res;
171
172     while (*len <= 0)
173     {   /* switch to next buffer */
174         if (*len < 0)
175             break;
176         from++;
177         len++;
178     }
179     if (*len > 0 && t->children && t->children[(unsigned char) **from])
180     {
181         const char *old_from = *from;
182         int old_len = *len;
183         
184         (*len)--;
185         (*from)++;
186         if ((res = find_entry_x(t->children[(unsigned char) *old_from],
187                                 from, len)))
188             return res;
189         /* no match */
190         *len = old_len;
191         *from = old_from;
192     }
193     /* no children match. use ourselves, if we have a target */
194     return t->target ? t : 0;
195 }
196
197 const char **chr_map_input_x(chrmaptab maptab, const char **from, int *len)
198 {
199     chr_t_entry *t = maptab->input;
200     chr_t_entry *res;
201
202     if (!(res = find_entry_x(t, from, len)))
203         abort();
204     return (const char **) (res->target);
205 }
206
207 const char **chr_map_input(chrmaptab maptab, const char **from, int len)
208 {
209     chr_t_entry *t = maptab->input;
210     chr_t_entry *res;
211     int len_tmp[2];
212
213     len_tmp[0] = len;
214     len_tmp[1] = -1;
215     if (!(res = find_entry_x(t, from, len_tmp)))
216         abort();
217     return (const char **) (res->target);
218 }
219
220 const char *chr_map_output(chrmaptab maptab, const char **from, int len)
221 {
222     unsigned char c = ** (unsigned char **) from;
223     (*from)++;
224     return (const char*) maptab->output[c];
225 }
226
227 unsigned char zebra_prim(char **s)
228 {
229     unsigned char c;
230     unsigned int i;
231     
232     if (**s == '\\')
233     {
234         (*s)++;
235         c = **s;
236         switch (c)
237         {
238         case '\\': c = '\\'; (*s)++; break;
239         case 'r': c = '\r'; (*s)++; break;
240         case 'n': c = '\n'; (*s)++; break;
241         case 't': c = '\t'; (*s)++; break;
242         case 's': c = ' '; (*s)++; break;
243         case 'x': sscanf(*s, "x%2x", &i); c = i; *s += 3; break;
244         case '{': case '[': case '(': case '}': case ']': case ')': case '$':
245             (*s)++;
246             break;
247         default:
248             sscanf(*s, "%3o", &i); c = i; *s += 3; break;
249         }
250         return c;
251     }
252     c = **s;
253     ++(*s);
254     return c;
255 }
256
257 /*
258  * Callback function.
259  * Add an entry to the value space.
260  */
261 static void fun_addentry(const char *s, void *data, int num)
262 {
263     chrmaptab tab = (chrmaptab) data;
264     char tmp[2];
265     
266     tmp[0] = num; tmp[1] = '\0';
267     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s), tmp);
268     tab->output[num + tab->base_uppercase] =
269         (unsigned char *) nmem_strdup(tab->nmem, s);
270 }
271
272 /* 
273  * Callback function.
274  * Add a space-entry to the value space.
275  */
276 static void fun_addspace(const char *s, void *data, int num)
277 {
278     chrmaptab tab = (chrmaptab) data;
279     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
280                                 (char*) CHR_SPACE);
281 }
282
283 /*
284  * Create a string containing the mapped characters provided.
285  */
286 static void fun_mkstring(const char *s, void *data, int num)
287 {
288     chrwork *arg = (chrwork *) data;
289     const char **res, *p = s;
290
291     res = chr_map_input(arg->map, &s, strlen(s));
292     if (*res == (char*) CHR_UNKNOWN)
293         logf(LOG_WARN, "Map: '%s' has no mapping", p);
294     strncat(arg->string, *res, CHR_MAXSTR - strlen(arg->string));
295     arg->string[CHR_MAXSTR] = '\0';
296 }
297
298 /*
299  * Add a map to the string contained in the argument.
300  */
301 static void fun_add_map(const char *s, void *data, int num)
302 {
303     chrwork *arg = (chrwork *) data;
304
305     assert(arg->map->input);
306     logf (LOG_LOG, "set map %.*s", (int) strlen(s), s);
307     set_map_string(arg->map->input, arg->map->nmem, s, strlen(s), arg->string);
308     for (s = arg->string; *s; s++)
309         logf (LOG_LOG, " %3d", (unsigned char) *s);
310 }
311
312 /*
313  * Add a query map to the string contained in the argument.
314  */
315 static void fun_add_qmap(const char *s, void *data, int num)
316 {
317     chrwork *arg = (chrwork *) data;
318
319     assert(arg->map->q_input);
320     logf (LOG_LOG, "set qmap %.*s", (int) strlen(s), s);
321     set_map_string(arg->map->q_input, arg->map->nmem, s,
322                    strlen(s), arg->string);
323     for (s = arg->string; *s; s++)
324         logf (LOG_LOG, " %3d", (unsigned char) *s);
325 }
326
327
328 static int scan_string(char *s,
329                        void (*fun)(const char *c, void *data, int num),
330                        void *data, int *num)
331 {
332     unsigned char c, str[1024], begin, end, *p;
333     
334     while (*s)
335     {
336         switch (*s)
337         {
338         case '{':
339             s++;
340             begin = zebra_prim(&s);
341             if (*s != '-')
342             {
343                 logf(LOG_FATAL, "Bad range in char-map");
344                 return -1;
345             }
346             s++;
347             end = zebra_prim(&s);
348             if (end <= begin)
349             {
350                 logf(LOG_FATAL, "Bad range in char-map");
351                 return -1;
352             }
353             s++;
354             for (c = begin; c <= end; c++)
355             {
356                 str[0] = c; str[1] = '\0';
357                 (*fun)((char *) str, data, num ? (*num)++ : 0);
358             }
359             break;
360         case '[': s++; abort(); break;
361         case '(':
362             p = (unsigned char*) ++s;
363                 /* Find the end-marker, ignoring escapes */
364             do
365             {
366                 if (!(p = (unsigned char*) strchr((char*) p, ')')))
367                 {
368                     logf(LOG_FATAL, "Missing ')' in string");
369                     return -1;
370                 }
371             }
372             while (*(p -  1) == '\\');
373             *p = 0;
374             (*fun)(s, data, num ? (*num)++ : 0);
375             s = (char*) p + 1;
376             break;
377         default:
378             c = zebra_prim(&s);
379             str[0] = c; str[1] = '\0';
380             (*fun)((char *) str, data, num ? (*num)++ : 0);
381         }
382     }
383     return 0;
384 }
385
386 chrmaptab chrmaptab_create(const char *tabpath, const char *name, int map_only)
387 {
388     FILE *f;
389     char line[512], *argv[50];
390     chrmaptab res;
391     int lineno = 0;
392     int errors = 0;
393     int argc, num = (int) *CHR_BASE, i;
394     NMEM nmem;
395
396     logf (LOG_LOG, "maptab %s open", name);
397     if (!(f = yaz_path_fopen(tabpath, name, "r")))
398     {
399         logf(LOG_WARN|LOG_ERRNO, "%s", name);
400         return 0;
401     }
402     nmem = nmem_create ();
403     res = (chrmaptab) nmem_malloc(nmem, sizeof(*res));
404     res->nmem = nmem;
405     res->input = (chr_t_entry *) nmem_malloc(res->nmem, sizeof(*res->input));
406     res->input->target = (unsigned char **)
407         nmem_malloc(res->nmem, sizeof(*res->input->target) * 2);
408     res->input->target[0] = (unsigned char*) CHR_UNKNOWN;
409     res->input->target[1] = 0;
410     res->input->children = (chr_t_entry **)
411         nmem_malloc(res->nmem, sizeof(res->input) * 256);
412     for (i = 0; i < 256; i++)
413     {
414         res->input->children[i] = (chr_t_entry *)
415             nmem_malloc(res->nmem, sizeof(*res->input));
416         res->input->children[i]->children = 0;
417         res->input->children[i]->target = (unsigned char **)
418             nmem_malloc (res->nmem, 2 * sizeof(unsigned char *));
419         res->input->children[i]->target[1] = 0;
420         if (map_only)
421         {
422             res->input->children[i]->target[0] = (unsigned char *)
423                 nmem_malloc (res->nmem, 2 * sizeof(unsigned char));
424             res->input->children[i]->target[0][0] = i;
425             res->input->children[i]->target[0][1] = 0;
426         }
427         else
428             res->input->children[i]->target[0] = (unsigned char*) CHR_UNKNOWN;
429     }
430     res->q_input = (chr_t_entry *)
431         nmem_malloc(res->nmem, sizeof(*res->q_input));
432     res->q_input->target = 0;
433     res->q_input->children = 0;
434
435     for (i = *CHR_BASE; i < 256; i++)
436         res->output[i] = 0;
437     res->output[(int) *CHR_SPACE] = (unsigned char *) " ";
438     res->output[(int) *CHR_UNKNOWN] = (unsigned char*) "@";
439     res->base_uppercase = 0;
440
441     while (!errors && (argc = readconf_line(f, &lineno, line, 512, argv, 50)))
442         if (!map_only && !yaz_matchstr(argv[0], "lowercase"))
443         {
444             if (argc != 2)
445             {
446                 logf(LOG_FATAL, "Syntax error in charmap");
447                 ++errors;
448             }
449             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
450             {
451                 logf(LOG_FATAL, "Bad value-set specification");
452                 ++errors;
453             }
454             res->base_uppercase = num;
455             res->output[(int) *CHR_SPACE + num] = (unsigned char *) " ";
456             res->output[(int) *CHR_UNKNOWN + num] = (unsigned char*) "@";
457             num = (int) *CHR_BASE;
458         }
459         else if (!map_only && !yaz_matchstr(argv[0], "uppercase"))
460         {
461             if (!res->base_uppercase)
462             {
463                 logf(LOG_FATAL, "Uppercase directive with no lowercase set");
464                 ++errors;
465             }
466             if (argc != 2)
467             {
468                 logf(LOG_FATAL, "Missing arg for uppercase directive");
469                 ++errors;
470             }
471             if (scan_string(argv[1], fun_addentry, res, &num) < 0)
472             {
473                 logf(LOG_FATAL, "Bad value-set specification");
474                 ++errors;
475             }
476         }
477         else if (!map_only && !yaz_matchstr(argv[0], "space"))
478         {
479             if (argc != 2)
480             {
481                 logf(LOG_FATAL, "Syntax error in charmap");
482                 ++errors;
483             }
484             if (scan_string(argv[1], fun_addspace, res, 0) < 0)
485             {
486                 logf(LOG_FATAL, "Bad space specification");
487                 ++errors;
488             }
489         }
490         else if (!yaz_matchstr(argv[0], "map"))
491         {
492             chrwork buf;
493
494             if (argc != 3)
495             {
496                 logf(LOG_FATAL, "charmap directive map requires 2 args");
497                 ++errors;
498             }
499             buf.map = res;
500             buf.string[0] = '\0';
501             if (scan_string(argv[2], fun_mkstring, &buf, 0) < 0)
502             {
503                 logf(LOG_FATAL, "Bad map target");
504                 ++errors;
505             }
506             if (scan_string(argv[1], fun_add_map, &buf, 0) < 0)
507             {
508                 logf(LOG_FATAL, "Bad map source");
509                 ++errors;
510             }
511         }
512         else if (!yaz_matchstr(argv[0], "qmap"))
513         {
514             chrwork buf;
515
516             if (argc != 3)
517             {
518                 logf(LOG_FATAL, "charmap directive qmap requires 2 args");
519                 ++errors;
520             }
521             buf.map = res;
522             buf.string[0] = '\0';
523             if (scan_string(argv[2], fun_mkstring, &buf, 0) < 0)
524             {
525                 logf(LOG_FATAL, "Bad qmap target");
526                 ++errors;
527             }
528             if (scan_string(argv[1], fun_add_qmap, &buf, 0) < 0)
529             {
530                 logf(LOG_FATAL, "Bad qmap source");
531                 ++errors;
532             }
533         }
534         else
535         {
536             logf(LOG_WARN, "Syntax error at '%s' in %s", line, name);
537         }
538     
539     fclose(f);
540     if (errors)
541     {
542         chrmaptab_destroy(res);
543         res = 0;
544     }
545     logf (LOG_LOG, "maptab %s close %d errors", name, errors);
546     return res;
547 }
548
549 void chrmaptab_destroy(chrmaptab tab)
550 {
551     if (tab)
552         nmem_destroy (tab->nmem);
553 }
554
555