c1e95f905f2ea126dd3e647141725ddd44fccdff
[idzebra-moved-to-github.git] / util / charmap.c
1 /* This file is part of the Zebra server.
2    Copyright (C) 1994-2010 Index Data
3
4 Zebra is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20
21 /**
22  * \file charmap.c
23  * \brief character conversions (.chr)
24  *  
25  * Support module to handle character-conversions into and out of the
26  * Zebra dictionary.
27  */
28
29 #include <ctype.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <assert.h>
33
34 typedef unsigned ucs4_t;
35
36 #include <charmap.h>
37
38 #include <yaz/yaz-util.h>
39
40 #define CHR_MAXSTR 1024
41 #define CHR_MAXEQUIV 32
42
43 const unsigned char CHR_FIELD_BEGIN = '^';
44
45 const char *CHR_UNKNOWN = "\001";
46 const char *CHR_SPACE   = "\002";
47 const char *CHR_CUT     = "\003";
48 const char *CHR_BASE    = "\005"; /* CHECK CHR_BASE_CHAR as well */
49
50 struct chrmaptab_info
51 {
52     chr_t_entry *input;         /* mapping table for input data */
53     chr_t_entry *q_input;       /* mapping table for queries */
54     unsigned char *output[256]; /* return mapping - for display of registers */
55     int base_uppercase;         /* Start of upper-case ordinals */
56     NMEM nmem;
57 };
58
59 /*
60  * Character map trie node.
61  */
62 struct chr_t_entry
63 {
64     chr_t_entry **children;  /* array of children */
65     unsigned char **target;  /* target for this node, if any */
66 };
67
68 /*
69  * General argument structure for callback functions (internal use only)
70  */
71 typedef struct chrwork 
72 {
73     chrmaptab map;
74     char string[CHR_MAXSTR+1];
75 } chrwork;
76
77 /*
78  * Callback for equivalent stuff
79  */
80 typedef struct
81 {
82     NMEM nmem;
83     int no_eq;
84     char *eq[CHR_MAXEQUIV];
85 } chr_equiv_work;
86 /*
87  * Add an entry to the character map.
88  */
89 static chr_t_entry *set_map_string(chr_t_entry *root, NMEM nmem,
90                                    const char *from, int len, char *to,
91                                    const char *from_0)
92 {
93     if (!from_0)
94         from_0 = from;
95     if (!root)
96     {
97         root = (chr_t_entry *) nmem_malloc(nmem, sizeof(*root));
98         root->children = 0;
99         root->target = 0;
100     }
101     if (!len)
102     {
103         if (!root->target || !root->target[0] || 
104             strcmp((const char *) root->target[0], to))
105         {
106             if (from_0 && 
107                 root->target && root->target[0] && root->target[0][0] &&
108                 strcmp((const char *) root->target[0], CHR_UNKNOWN))
109             {
110                 yaz_log(YLOG_WARN, "duplicate entry for charmap from '%s'",
111                         from_0);
112             }
113             root->target = (unsigned char **)
114                 nmem_malloc(nmem, sizeof(*root->target)*2);
115             root->target[0] = (unsigned char *) nmem_strdup(nmem, to);
116             root->target[1] = 0;
117         }
118     }
119     else
120     {
121         if (!root->children)
122         {
123             int i;
124
125             root->children = (chr_t_entry **)
126                 nmem_malloc(nmem, sizeof(chr_t_entry*) * 256);
127             for (i = 0; i < 256; i++)
128                 root->children[i] = 0;
129         }
130         if (!(root->children[(unsigned char) *from] =
131               set_map_string(root->children[(unsigned char) *from], nmem,
132                              from + 1, len - 1, to, from_0)))
133             return 0;
134     }
135     return root;
136 }
137
138 static chr_t_entry *find_entry_x(chr_t_entry *t, const char **from, int *len, int first)
139 {
140     chr_t_entry *res;
141
142     while (*len <= 0)
143     {   /* switch to next buffer */
144         if (*len < 0)
145             break;
146         from++;
147         len++;
148     }
149     if (*len > 0 && t->children)
150     {
151         const char *old_from = *from;
152         int old_len = *len;
153
154         res = 0;
155
156         if (first && t->children[CHR_FIELD_BEGIN])
157         {
158             if ((res = find_entry_x(t->children[CHR_FIELD_BEGIN], from, len, 0)) && res != t->children[CHR_FIELD_BEGIN])
159                 return res;
160             else
161                 res = 0;
162             /* otherwhise there was no match on beginning of field, move on */
163         } 
164         
165         if (!res && t->children[(unsigned char) **from])
166         {
167             (*len)--;
168             (*from)++;
169             if ((res = find_entry_x(t->children[(unsigned char) *old_from],
170                                     from, len, 0)))
171                 return res;
172             /* no match */
173             *len = old_len;
174             *from = old_from;
175         }
176     }
177     /* no children match. use ourselves, if we have a target */
178     return t->target ? t : 0;
179 }
180
181 const char **chr_map_input_x(chrmaptab maptab, const char **from, int *len, int first)
182 {
183     chr_t_entry *t = maptab->input;
184     chr_t_entry *res;
185
186     if (!(res = find_entry_x(t, from, len, first)))
187         abort();
188     return (const char **) (res->target);
189 }
190
191 const char **chr_map_input(chrmaptab maptab, const char **from, int len, int first)
192 {
193     chr_t_entry *t = maptab->input;
194     chr_t_entry *res;
195     int len_tmp[2];
196
197     len_tmp[0] = len;
198     len_tmp[1] = -1;
199     if (!(res = find_entry_x(t, from, len_tmp, first)))
200         abort();
201     return (const char **) (res->target);
202 }
203
204 const char **chr_map_q_input(chrmaptab maptab,
205                              const char **from, int len, int first)
206 {
207     chr_t_entry *t = maptab->q_input;
208     chr_t_entry *res;
209     int len_tmp[2];
210     
211     len_tmp[0] = len;
212     len_tmp[1] = -1;
213     if (!(res = find_entry_x(t, from, len_tmp, first)))
214         return 0;
215     return (const char **) (res->target);
216 }
217
218 const char *chr_map_output(chrmaptab maptab, const char **from, int len)
219 {
220     unsigned char c = ** (unsigned char **) from;
221     const char *out = (const char*) maptab->output[c];
222
223     if (out)
224         (*from)++;
225     return out;
226 }
227
228 static int zebra_ucs4_strlen(ucs4_t *s)
229 {
230     int i = 0;
231     while (*s++)
232         i++;
233     return i;
234 }
235
236 ucs4_t zebra_prim_w(ucs4_t **s)
237 {
238     ucs4_t c;
239     ucs4_t i = 0;
240     char fmtstr[8];
241
242     yaz_log(YLOG_DEBUG, "prim_w %.3s", (char *) *s);
243     if (**s == '\\' && 1[*s])
244     {
245         (*s)++;
246         c = **s;
247         switch (c)
248         {
249         case '\\': c = '\\'; (*s)++; break;
250         case 'r': c = '\r'; (*s)++; break;
251         case 'n': c = '\n'; (*s)++; break;
252         case 't': c = '\t'; (*s)++; break;
253         case 's': c = ' '; (*s)++; break;
254         case 'x': 
255             if (zebra_ucs4_strlen(*s) >= 3)
256             {
257                 fmtstr[0] = (*s)[1];
258                 fmtstr[1] = (*s)[2];
259                 fmtstr[2] = 0;
260                 sscanf(fmtstr, "%x", &i);
261                 c = i;
262                 *s += 3;
263             }
264             break;
265         case '0':
266         case '1':
267         case '2':
268         case '3':
269         case '4':
270         case '5':
271         case '6':
272         case '7':
273         case '8':
274         case '9':
275             if (zebra_ucs4_strlen(*s) >= 3)
276             {
277                 fmtstr[0] = (*s)[0];
278                 fmtstr[1] = (*s)[1];
279                 fmtstr[2] = (*s)[2];
280                 fmtstr[3] = 0;
281                 sscanf(fmtstr, "%o", &i);
282                 c = i;
283                 *s += 3;
284             }
285             break;
286         case 'L':
287             if (zebra_ucs4_strlen(*s) >= 5)
288             {
289                 fmtstr[0] = (*s)[1];
290                 fmtstr[1] = (*s)[2];
291                 fmtstr[2] = (*s)[3];
292                 fmtstr[3] = (*s)[4];
293                 fmtstr[4] = 0;
294                 sscanf(fmtstr, "%x", &i);
295                 c = i;
296                 *s += 5;
297             }
298             break;
299         default:
300             (*s)++;
301         }
302     }
303     else
304     {
305         c = **s;
306         ++(*s);
307     }
308     yaz_log(YLOG_DEBUG, "out %d", c);
309     return c;
310 }
311
312 /*
313  * Callback function.
314  * Add an entry to the value space.
315  */
316 static void fun_addentry(const char *s, void *data, int num)
317 {
318     chrmaptab tab = (chrmaptab) data;
319     char tmp[2];
320     
321     tmp[0] = num; tmp[1] = '\0';
322     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s), tmp, 0);
323     tab->output[num + tab->base_uppercase] =
324         (unsigned char *) nmem_strdup(tab->nmem, s);
325 }
326
327 /* 
328  * Callback function.
329  * Add a space-entry to the value space.
330  */
331 static void fun_addspace(const char *s, void *data, int num)
332 {
333     chrmaptab tab = (chrmaptab) data;
334     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
335                                 (char*) CHR_SPACE, 0);
336 }
337
338 /* 
339  * Callback function.
340  * Add a space-entry to the value space.
341  */
342 static void fun_addcut(const char *s, void *data, int num)
343 {
344     chrmaptab tab = (chrmaptab) data;
345     tab->input = set_map_string(tab->input, tab->nmem, s, strlen(s),
346                                 (char*) CHR_CUT, 0);
347 }
348
349 /*
350  * Create a string containing the mapped characters provided.
351  */
352 static void fun_mkstring(const char *s, void *data, int num)
353 {
354     chrwork *arg = (chrwork *) data;
355     const char **res, *p = s;
356
357     res = chr_map_input(arg->map, &s, strlen(s), 0);
358     if (*res == (char*) CHR_UNKNOWN)
359         yaz_log(YLOG_WARN, "Map: '%s' has no mapping", p);
360     strncat(arg->string, *res, CHR_MAXSTR - strlen(arg->string));
361     arg->string[CHR_MAXSTR] = '\0';
362 }
363
364 /*
365  * Create an unmodified string (scan_string handler).
366  */
367 static void fun_add_equivalent_string(const char *s, void *data, int num)
368 {
369     chr_equiv_work *arg = (chr_equiv_work *) data;
370     
371     if (arg->no_eq == CHR_MAXEQUIV)
372         return;
373     arg->eq[arg->no_eq++] = nmem_strdup(arg->nmem, s);
374 }
375
376 /*
377  * Add a map to the string contained in the argument.
378  */
379 static void fun_add_map(const char *s, void *data, int num)
380 {
381     chrwork *arg = (chrwork *) data;
382
383     assert(arg->map->input);
384     yaz_log(YLOG_DEBUG, "set map %.*s", (int) strlen(s), s);
385     set_map_string(arg->map->input, arg->map->nmem, s, strlen(s), arg->string,
386                    0);
387     for (s = arg->string; *s; s++)
388         yaz_log(YLOG_DEBUG, " %3d", (unsigned char) *s);
389 }
390
391 static int scan_to_utf8(yaz_iconv_t t, ucs4_t *from, size_t inlen,
392                         char *outbuf, size_t outbytesleft)
393 {
394     size_t inbytesleft = inlen * sizeof(ucs4_t);
395     char *inbuf = (char*) from;
396     size_t ret;
397    
398     if (t == 0)
399         *outbuf++ = *from;  /* ISO-8859-1 is OK here */
400     else
401     {
402         ret = yaz_iconv(t, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
403         if (ret != (size_t) (-1))
404             ret = yaz_iconv(t, 0, 0, &outbuf, &outbytesleft);
405         
406             
407         if (ret == (size_t) (-1))
408         {
409             yaz_log(YLOG_LOG, "from: %2X %2X %2X %2X",
410                     from[0], from[1], from[2], from[3]);
411             yaz_log(YLOG_WARN|YLOG_ERRNO, "bad unicode sequence");
412             return -1;
413         }
414     }
415     *outbuf = '\0';
416     return 0;
417 }
418
419 static int scan_string(char *s_native,
420                        yaz_iconv_t t_unicode, yaz_iconv_t t_utf8,
421                        void (*fun)(const char *c, void *data, int num),
422                        void *data, int *num)
423 {
424     char str[1024];
425
426     ucs4_t arg[512];
427     ucs4_t arg_prim[512];
428     ucs4_t *s0, *s = arg;
429     ucs4_t c, begin, end;
430     size_t i;
431
432     if (t_unicode != 0)
433     {
434         char *outbuf = (char *) arg;
435         char *inbuf = s_native;
436         size_t outbytesleft = sizeof(arg)-4;
437         size_t inbytesleft = strlen(s_native);
438         size_t ret;             
439         ret = yaz_iconv(t_unicode, &inbuf, &inbytesleft,
440                         &outbuf, &outbytesleft);
441         if (ret != (size_t)(-1))
442             ret = yaz_iconv(t_unicode, 0, 0, &outbuf, &outbytesleft);
443             
444         if (ret == (size_t)(-1))
445             return -1;
446         i = (outbuf - (char*) arg)/sizeof(ucs4_t);
447     }
448     else
449     { 
450         for (i = 0; s_native[i]; i++)
451             arg[i] = s_native[i] & 255; /* ISO-8859-1 conversion */
452     }
453     arg[i] = 0;      /* terminate */
454     if (s[0] == 0xfeff || s[0] == 0xfeff)  /* skip byte Order Mark */
455         s++;
456     while (*s)
457     {
458         switch (*s)
459         {
460         case '{':
461             s++;
462             begin = zebra_prim_w(&s);
463             if (*s != '-')
464             {
465                 yaz_log(YLOG_FATAL, "Bad range in char-map");
466                 return -1;
467             }
468             s++;
469             end = zebra_prim_w(&s);
470             if (end <= begin)
471             {
472                 yaz_log(YLOG_FATAL, "Bad range in char-map");
473                 return -1;
474             }
475             s++;
476             for (c = begin; c <= end; c++)
477             {
478                 if (scan_to_utf8(t_utf8, &c, 1, str, sizeof(str)-1))
479                     return -1;
480                 (*fun)(str, data, num ? (*num)++ : 0);
481             }
482             break;
483         case '(':
484             ++s;
485             s0 = s; i = 0;
486             while (*s != ')' || s[-1] == '\\')
487                 arg_prim[i++] = zebra_prim_w(&s);
488             arg_prim[i] = 0;
489             if (scan_to_utf8(t_utf8, arg_prim, zebra_ucs4_strlen(arg_prim), str, sizeof(str)-1))
490                 return -1;
491             (*fun)(str, data, num ? (*num)++ : 0);
492             s++;
493             break;
494         default:
495             c = zebra_prim_w(&s);
496             if (scan_to_utf8(t_utf8, &c, 1, str, sizeof(str)-1))
497                 return -1;
498             (*fun)(str, data, num ? (*num)++ : 0);
499         }
500     }
501     return 0;
502 }
503
504 chrmaptab chrmaptab_create(const char *tabpath, const char *name,
505                            const char *tabroot)
506 {
507     FILE *f;
508     char line[512], *argv[50];
509     chrmaptab res;
510     int lineno = 0;
511     int no_directives = 0;
512     int errors = 0;
513     int argc, num = (int) *CHR_BASE, i;
514     NMEM nmem;
515     yaz_iconv_t t_unicode = 0;
516     yaz_iconv_t t_utf8 = 0;
517     unsigned endian = 31;
518     const char *ucs4_native = "UCS-4";
519
520     yaz_log(YLOG_DEBUG, "maptab %s open", name);
521     if (!(f = yaz_fopen(tabpath, name, "r", tabroot)))
522     {
523         yaz_log(YLOG_WARN|YLOG_ERRNO, "%s", name);
524         return 0;
525     }
526
527     if (*(char*) &endian == 31)      /* little endian? */
528         ucs4_native = "UCS-4LE";
529
530     t_utf8 = yaz_iconv_open("UTF-8", ucs4_native);
531
532     nmem = nmem_create();
533     res = (chrmaptab) nmem_malloc(nmem, sizeof(*res));
534     res->nmem = nmem;
535     res->input = (chr_t_entry *) nmem_malloc(res->nmem, sizeof(*res->input));
536     res->input->target = (unsigned char **)
537         nmem_malloc(res->nmem, sizeof(*res->input->target) * 2);
538     res->input->target[0] = (unsigned char*) CHR_UNKNOWN;
539     res->input->target[1] = 0;
540     res->input->children = (chr_t_entry **)
541         nmem_malloc(res->nmem, sizeof(res->input) * 256);
542     for (i = 0; i < 256; i++)
543     {
544         res->input->children[i] = (chr_t_entry *)
545             nmem_malloc(res->nmem, sizeof(*res->input));
546         res->input->children[i]->children = 0;
547         res->input->children[i]->target = (unsigned char **)
548             nmem_malloc(res->nmem, 2 * sizeof(unsigned char *));
549         res->input->children[i]->target[1] = 0;
550         res->input->children[i]->target[0] = (unsigned char*) CHR_UNKNOWN;
551     }
552     res->q_input = (chr_t_entry *)
553         nmem_malloc(res->nmem, sizeof(*res->q_input));
554     res->q_input->target = 0;
555     res->q_input->children = 0;
556
557     for (i = *CHR_BASE; i < 256; i++)
558         res->output[i] = 0;
559     res->output[(int) *CHR_SPACE] = (unsigned char *) " ";
560     res->output[(int) *CHR_UNKNOWN] = (unsigned char*) "@";
561     res->base_uppercase = 0;
562
563     while (!errors && (argc = readconf_line(f, &lineno, line, 512, argv, 50)))
564     {
565         no_directives++;
566         if (!yaz_matchstr(argv[0], "lowercase"))
567         {
568             if (argc != 2)
569             {
570                 yaz_log(YLOG_FATAL, "Syntax error in charmap");
571                 ++errors;
572             }
573             if (scan_string(argv[1], t_unicode, t_utf8, fun_addentry,
574                             res, &num) < 0)
575             {
576                 yaz_log(YLOG_FATAL, "Bad value-set specification");
577                 ++errors;
578             }
579             res->base_uppercase = num;
580             res->output[(int) *CHR_SPACE + num] = (unsigned char *) " ";
581             res->output[(int) *CHR_UNKNOWN + num] = (unsigned char*) "@";
582             num = (int) *CHR_BASE;
583         }
584         else if (!yaz_matchstr(argv[0], "uppercase"))
585         {
586             if (!res->base_uppercase)
587             {
588                 yaz_log(YLOG_FATAL, "Uppercase directive with no lowercase set");
589                 ++errors;
590             }
591             if (argc != 2)
592             {
593                 yaz_log(YLOG_FATAL, "Missing arg for uppercase directive");
594                 ++errors;
595             }
596             if (scan_string(argv[1], t_unicode, t_utf8, fun_addentry,
597                             res, &num) < 0)
598             {
599                 yaz_log(YLOG_FATAL, "Bad value-set specification");
600                 ++errors;
601             }
602         }
603         else if (!yaz_matchstr(argv[0], "space"))
604         {
605             if (argc != 2)
606             {
607                 yaz_log(YLOG_FATAL, "Syntax error in charmap for space");
608                 ++errors;
609             }
610             if (scan_string(argv[1], t_unicode, t_utf8,
611                             fun_addspace, res, 0) < 0)
612             {
613                 yaz_log(YLOG_FATAL, "Bad space specification");
614                 ++errors;
615             }
616         }
617         else if (!yaz_matchstr(argv[0], "cut"))
618         {
619             if (argc != 2)
620             {
621                 yaz_log(YLOG_FATAL, "Syntax error in charmap for cut");
622                 ++errors;
623             }
624             if (scan_string(argv[1], t_unicode, t_utf8,
625                             fun_addcut, res, 0) < 0)
626             {
627                 yaz_log(YLOG_FATAL, "Bad cut specification");
628                 ++errors;
629             }
630         }
631         else if (!yaz_matchstr(argv[0], "map"))
632         {
633             chrwork buf;
634
635             if (argc != 3)
636             {
637                 yaz_log(YLOG_FATAL, "charmap directive map requires 2 args");
638                 ++errors;
639             }
640             buf.map = res;
641             buf.string[0] = '\0';
642             if (scan_string(argv[2], t_unicode, t_utf8,
643                             fun_mkstring, &buf, 0) < 0)
644             {
645                 yaz_log(YLOG_FATAL, "Bad map target");
646                 ++errors;
647             }
648             if (scan_string(argv[1], t_unicode, t_utf8,
649                             fun_add_map, &buf, 0) < 0)
650             {
651                 yaz_log(YLOG_FATAL, "Bad map source");
652                 ++errors;
653             }
654         }
655         else if (!yaz_matchstr(argv[0], "equivalent"))
656         {
657             chr_equiv_work w;
658
659             if (argc != 2)
660             {
661                 yaz_log(YLOG_FATAL, "equivalent requires 1 argument");
662                 ++errors;
663             }
664             w.nmem = res->nmem;
665             w.no_eq = 0;
666             if (scan_string(argv[1], t_unicode, t_utf8, 
667                             fun_add_equivalent_string, &w, 0) < 0)
668             {
669                 yaz_log(YLOG_FATAL, "equivalent: invalid string");
670                 ++errors;
671             }
672             else if (w.no_eq == 0)
673             {
674                 yaz_log(YLOG_FATAL, "equivalent: no strings");
675                 ++errors;
676             }
677             else
678             {
679                 char *result_str;
680                 int i, slen = 5;
681
682                 /* determine length of regular expression */
683                 for (i = 0; i<w.no_eq; i++)
684                     slen += strlen(w.eq[i]) + 1;
685                 result_str = nmem_malloc(res->nmem, slen + 5);
686
687                 /* build the regular expression */
688                 *result_str = '\0';
689                 slen = 0;
690                 for (i = 0; i<w.no_eq; i++)
691                 {
692                     result_str[slen++]  = i ? '|' : '(';
693                     strcpy(result_str + slen, w.eq[i]);
694                     slen += strlen(w.eq[i]);
695                 }
696                 result_str[slen++] = ')';
697                 result_str[slen] = '\0';
698
699                 /* each eq will map to this regular expression */
700                 for (i = 0; i<w.no_eq; i++)
701                 {
702                     set_map_string(res->q_input, res->nmem,
703                                    w.eq[i], strlen(w.eq[i]),
704                                    result_str, 0);
705                 }
706             }
707         }
708         else if (!yaz_matchstr(argv[0], "encoding"))
709         {
710             if (t_unicode != 0)
711                 yaz_iconv_close(t_unicode);
712             t_unicode = yaz_iconv_open(ucs4_native, argv[1]);
713         }
714         else
715         {
716             yaz_log(YLOG_WARN, "Syntax error at '%s' in %s", line, name);
717             errors++;
718         }
719     }   
720     yaz_fclose(f);
721     if (no_directives == 0)
722     {
723         yaz_log(YLOG_WARN, "No directives in '%s'", name);
724         errors++;
725     }
726     if (errors)
727     {
728         chrmaptab_destroy(res);
729         res = 0;
730     }
731     yaz_log(YLOG_DEBUG, "maptab %s close %d errors", name, errors);
732     if (t_utf8 != 0)
733         yaz_iconv_close(t_utf8);
734     if (t_unicode != 0)
735         yaz_iconv_close(t_unicode);
736     return res;
737 }
738
739 void chrmaptab_destroy(chrmaptab tab)
740 {
741     if (tab)
742         nmem_destroy(tab->nmem);
743 }
744
745
746 /*
747  * Local variables:
748  * c-basic-offset: 4
749  * c-file-style: "Stroustrup"
750  * indent-tabs-mode: nil
751  * End:
752  * vim: shiftwidth=4 tabstop=8 expandtab
753  */
754