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