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