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