Fixed bug #416: First/second comb characters not handled in the
[yaz-moved-to-github.git] / src / siconv.c
1 /*
2  * Copyright (C) 1995-2005, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: siconv.c,v 1.14 2005-10-28 18:36:58 adam Exp $
6  */
7 /**
8  * \file siconv.c
9  * \brief Implements simple ICONV
10  *
11  * This implements an interface similar to that of iconv and
12  * is used by YAZ to interface with iconv (if present).
13  * For systems where iconv is not present, this layer
14  * provides a few important conversion: UTF-8, MARC-8, Latin-1.
15  */
16
17 #if HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20
21 #include <errno.h>
22 #include <string.h>
23 #include <ctype.h>
24 #if HAVE_WCHAR_H
25 #include <wchar.h>
26 #endif
27
28 #if HAVE_ICONV_H
29 #include <iconv.h>
30 #endif
31
32 #include <yaz/yaz-util.h>
33
34 unsigned long yaz_marc8_1_conv (unsigned char *inp, size_t inbytesleft,
35                               size_t *no_read, int *combining);
36 unsigned long yaz_marc8_2_conv (unsigned char *inp, size_t inbytesleft,
37                                 size_t *no_read, int *combining);
38 unsigned long yaz_marc8_3_conv (unsigned char *inp, size_t inbytesleft,
39                                 size_t *no_read, int *combining);
40 unsigned long yaz_marc8_4_conv (unsigned char *inp, size_t inbytesleft,
41                                 size_t *no_read, int *combining);
42 unsigned long yaz_marc8_5_conv (unsigned char *inp, size_t inbytesleft,
43                                 size_t *no_read, int *combining);
44 unsigned long yaz_marc8_6_conv (unsigned char *inp, size_t inbytesleft,
45                                 size_t *no_read, int *combining);
46 unsigned long yaz_marc8_7_conv (unsigned char *inp, size_t inbytesleft,
47                                 size_t *no_read, int *combining);
48 unsigned long yaz_marc8_8_conv (unsigned char *inp, size_t inbytesleft,
49                                 size_t *no_read, int *combining);
50 unsigned long yaz_marc8_9_conv (unsigned char *inp, size_t inbytesleft,
51                                 size_t *no_read, int *combining);
52     
53 #define NEW_COMB 1
54
55 struct yaz_iconv_struct {
56     int my_errno;
57     int init_flag;
58     size_t (*init_handle)(yaz_iconv_t cd, unsigned char *inbuf,
59                           size_t inbytesleft, size_t *no_read);
60     unsigned long (*read_handle)(yaz_iconv_t cd, unsigned char *inbuf,
61                                  size_t inbytesleft, size_t *no_read);
62     size_t (*write_handle)(yaz_iconv_t cd, unsigned long x,
63                            char **outbuf, size_t *outbytesleft,
64                            int last);
65     int marc8_esc_mode;
66 #if NEW_COMB
67     int comb_offset;
68     int comb_size;
69     unsigned long comb_x[8];
70     size_t comb_no_read[8];
71 #else
72     int marc8_comb_x;
73     int marc8_comb_no_read;
74 #endif
75     size_t no_read_x;
76     unsigned long unget_x;
77 #if HAVE_ICONV_H
78     iconv_t iconv_cd;
79 #endif
80     unsigned long compose_char;
81 };
82
83 static unsigned long yaz_read_ISO8859_1 (yaz_iconv_t cd, unsigned char *inp,
84                                          size_t inbytesleft, size_t *no_read)
85 {
86     unsigned long x = inp[0];
87     *no_read = 1;
88     return x;
89 }
90
91 static size_t yaz_init_UTF8 (yaz_iconv_t cd, unsigned char *inp,
92                              size_t inbytesleft, size_t *no_read)
93 {
94     if (inp[0] != 0xef)
95     {
96         *no_read = 0;
97         return 0;
98     }
99     if (inbytesleft < 3)
100     {
101         cd->my_errno = YAZ_ICONV_EINVAL;
102         return (size_t) -1;
103     }
104     if (inp[1] != 0xbb || inp[2] != 0xbf)
105     {
106         cd->my_errno = YAZ_ICONV_EILSEQ;
107         return (size_t) -1;
108     }
109     *no_read = 3;
110     return 0;
111 }
112
113 static unsigned long yaz_read_UTF8 (yaz_iconv_t cd, unsigned char *inp,
114                                     size_t inbytesleft, size_t *no_read)
115 {
116     unsigned long x = 0;
117
118     if (inp[0] <= 0x7f)
119     {
120         x = inp[0];
121         *no_read = 1;
122     }
123     else if (inp[0] <= 0xbf || inp[0] >= 0xfe)
124     {
125         *no_read = 0;
126         cd->my_errno = YAZ_ICONV_EILSEQ;
127     }
128     else if (inp[0] <= 0xdf && inbytesleft >= 2)
129     {
130         x = ((inp[0] & 0x1f) << 6) | (inp[1] & 0x3f);
131         if (x >= 0x80)
132             *no_read = 2;
133         else
134         {
135             *no_read = 0;
136             cd->my_errno = YAZ_ICONV_EILSEQ;
137         }
138     }
139     else if (inp[0] <= 0xef && inbytesleft >= 3)
140     {
141         x = ((inp[0] & 0x0f) << 12) | ((inp[1] & 0x3f) << 6) |
142             (inp[1] & 0x3f);
143         if (x >= 0x800)
144             *no_read = 3;
145         else
146         {
147             *no_read = 0;
148             cd->my_errno = YAZ_ICONV_EILSEQ;
149         }
150     }
151     else if (inp[0] <= 0xf7 && inbytesleft >= 4)
152     {
153         x =  ((inp[0] & 0x07) << 18) | ((inp[1] & 0x3f) << 12) |
154             ((inp[2] & 0x3f) << 6) | (inp[3] & 0x3f);
155         if (x >= 0x10000)
156             *no_read = 4;
157         else
158         {
159             *no_read = 0;
160             cd->my_errno = YAZ_ICONV_EILSEQ;
161         }
162     }
163     else if (inp[0] <= 0xfb && inbytesleft >= 5)
164     {
165         x =  ((inp[0] & 0x03) << 24) | ((inp[1] & 0x3f) << 18) |
166             ((inp[2] & 0x3f) << 12) | ((inp[3] & 0x3f) << 6) |
167             (inp[4] & 0x3f);
168         if (x >= 0x200000)
169             *no_read = 5;
170         else
171         {
172             *no_read = 0;
173             cd->my_errno = YAZ_ICONV_EILSEQ;
174         }
175     }
176     else if (inp[0] <= 0xfd && inbytesleft >= 6)
177     {
178         x =  ((inp[0] & 0x01) << 30) | ((inp[1] & 0x3f) << 24) |
179             ((inp[2] & 0x3f) << 18) | ((inp[3] & 0x3f) << 12) |
180             ((inp[4] & 0x3f) << 6) | (inp[5] & 0x3f);
181         if (x >= 0x4000000)
182             *no_read = 6;
183         else
184         {
185             *no_read = 0;
186             cd->my_errno = YAZ_ICONV_EILSEQ;
187         }
188     }
189     else
190     {
191         *no_read = 0;
192         cd->my_errno = YAZ_ICONV_EINVAL;
193     }
194     return x;
195 }
196
197 static unsigned long yaz_read_UCS4 (yaz_iconv_t cd, unsigned char *inp,
198                                     size_t inbytesleft, size_t *no_read)
199 {
200     unsigned long x = 0;
201     
202     if (inbytesleft < 4)
203     {
204         cd->my_errno = YAZ_ICONV_EINVAL; /* incomplete input */
205         *no_read = 0;
206     }
207     else
208     {
209         x = (inp[0]<<24) | (inp[1]<<16) | (inp[2]<<8) | inp[3];
210         *no_read = 4;
211     }
212     return x;
213 }
214
215 static unsigned long yaz_read_UCS4LE (yaz_iconv_t cd, unsigned char *inp,
216                                       size_t inbytesleft, size_t *no_read)
217 {
218     unsigned long x = 0;
219     
220     if (inbytesleft < 4)
221     {
222         cd->my_errno = YAZ_ICONV_EINVAL; /* incomplete input */
223         *no_read = 0;
224     }
225     else
226     {
227         x = (inp[3]<<24) | (inp[2]<<16) | (inp[1]<<8) | inp[0];
228         *no_read = 4;
229     }
230     return x;
231 }
232
233 #if HAVE_WCHAR_H
234 static unsigned long yaz_read_wchar_t (yaz_iconv_t cd, unsigned char *inp,
235                                        size_t inbytesleft, size_t *no_read)
236 {
237     unsigned long x = 0;
238     
239     if (inbytesleft < sizeof(wchar_t))
240     {
241         cd->my_errno = YAZ_ICONV_EINVAL; /* incomplete input */
242         *no_read = 0;
243     }
244     else
245     {
246         wchar_t wch;
247         memcpy (&wch, inp, sizeof(wch));
248         x = wch;
249         *no_read = sizeof(wch);
250     }
251     return x;
252 }
253 #endif
254
255
256 #if NEW_COMB
257 static unsigned long yaz_read_marc8_comb (yaz_iconv_t cd, unsigned char *inp,
258                                           size_t inbytesleft, size_t *no_read,
259                                           int *comb);
260
261 static unsigned long yaz_read_marc8 (yaz_iconv_t cd, unsigned char *inp,
262                                      size_t inbytesleft, size_t *no_read)
263 {
264     unsigned long x;
265     if (cd->comb_offset < cd->comb_size)
266     {
267         *no_read = cd->comb_no_read[cd->comb_offset];
268         x = cd->comb_x[cd->comb_offset];
269
270         /* special case for double-diacritic combining characters, 
271            INVERTED BREVE and DOUBLE TILDE.
272            We'll increment the no_read counter by 1, since we want to skip over
273            the processing of the closing ligature character
274         */
275         if (x == 0x0361 || x == 0x0360)
276             *no_read += 1;
277         cd->comb_offset++;
278         return x;
279     }
280
281     cd->comb_offset = 0;
282     for (cd->comb_size = 0; cd->comb_size < 8; cd->comb_size++)
283     {
284         int comb = 0;
285         x = yaz_read_marc8_comb(cd, inp, inbytesleft, no_read, &comb);
286         if (!comb || !x)
287             break;
288         cd->comb_x[cd->comb_size] = x;
289         cd->comb_no_read[cd->comb_size] = *no_read;
290         inp += *no_read;
291         inbytesleft = inbytesleft - *no_read;
292     }
293     return x;
294 }
295
296 static unsigned long yaz_read_marc8_comb (yaz_iconv_t cd, unsigned char *inp,
297                                           size_t inbytesleft, size_t *no_read,
298                                           int *comb)
299 {
300     *no_read = 0;
301     while(inbytesleft >= 1 && inp[0] == 27)
302     {
303         size_t inbytesleft0 = inbytesleft;
304         inp++;
305         inbytesleft--;
306         while(inbytesleft > 0 && strchr("(,$!", *inp))
307         {
308             inbytesleft--;
309             inp++;
310         }
311         if (inbytesleft <= 0)
312         {
313             *no_read = 0;
314             cd->my_errno = YAZ_ICONV_EINVAL;
315             return 0;
316         }
317         cd->marc8_esc_mode = *inp++;
318         inbytesleft--;
319         (*no_read) += inbytesleft0 - inbytesleft;
320     }
321     if (inbytesleft <= 0)
322         return 0;
323     else
324     {
325         unsigned long x;
326         size_t no_read_sub = 0;
327         *comb = 0;
328
329         switch(cd->marc8_esc_mode)
330         {
331         case 'B':  /* Basic ASCII */
332         case 'E':  /* ANSEL */
333         case 's':  /* ASCII */
334             x = yaz_marc8_1_conv(inp, inbytesleft, &no_read_sub, comb);
335             break;
336         case 'g':  /* Greek */
337             x = yaz_marc8_2_conv(inp, inbytesleft, &no_read_sub, comb);
338             break;
339         case 'b':  /* Subscripts */
340             x = yaz_marc8_3_conv(inp, inbytesleft, &no_read_sub, comb);
341             break;
342         case 'p':  /* Superscripts */
343             x = yaz_marc8_4_conv(inp, inbytesleft, &no_read_sub, comb);
344             break;
345         case '2':  /* Basic Hebrew */
346             x = yaz_marc8_5_conv(inp, inbytesleft, &no_read_sub, comb);
347             break;
348         case 'N':  /* Basic Cyrillic */
349         case 'Q':  /* Extended Cyrillic */
350             x = yaz_marc8_6_conv(inp, inbytesleft, &no_read_sub, comb);
351             break;
352         case '3':  /* Basic Arabic */
353         case '4':  /* Extended Arabic */
354             x = yaz_marc8_7_conv(inp, inbytesleft, &no_read_sub, comb);
355             break;
356         case 'S':  /* Greek */
357             x = yaz_marc8_8_conv(inp, inbytesleft, &no_read_sub, comb);
358             break;
359         case '1':  /* Chinese, Japanese, Korean (EACC) */
360             x = yaz_marc8_9_conv(inp, inbytesleft, &no_read_sub, comb);
361             break;
362         default:
363             *no_read = 0;
364             cd->my_errno = YAZ_ICONV_EILSEQ;
365             return 0;
366         }
367         *no_read += no_read_sub;
368         return x;
369     }
370 }
371 #else
372 static unsigned long yaz_read_marc8 (yaz_iconv_t cd, unsigned char *inp,
373                                      size_t inbytesleft, size_t *no_read)
374 {
375     if (cd->marc8_comb_x)
376     {
377         unsigned long x = cd->marc8_comb_x;
378         *no_read = cd->marc8_comb_no_read;
379         cd->marc8_comb_x = 0;
380         return x;
381     }
382     *no_read = 0;
383     while(inbytesleft >= 1 && inp[0] == 27)
384     {
385         size_t inbytesleft0 = inbytesleft;
386         inp++;
387         inbytesleft--;
388         while(inbytesleft > 0 && strchr("(,$!", *inp))
389         {
390             inbytesleft--;
391             inp++;
392         }
393         if (inbytesleft <= 0)
394         {
395             *no_read = 0;
396             cd->my_errno = YAZ_ICONV_EINVAL;
397             return 0;
398         }
399         cd->marc8_esc_mode = *inp++;
400         inbytesleft--;
401         (*no_read) += inbytesleft0 - inbytesleft;
402     }
403     if (inbytesleft <= 0)
404         return 0;
405     else
406     {
407         unsigned long x;
408         int comb = 0;
409         size_t no_read_sub = 0;
410
411         switch(cd->marc8_esc_mode)
412         {
413         case 'B':  /* Basic ASCII */
414         case 'E':  /* ANSEL */
415         case 's':  /* ASCII */
416             x = yaz_marc8_1_conv(inp, inbytesleft, &no_read_sub, &comb);
417             break;
418         case 'g':  /* Greek */
419             x = yaz_marc8_2_conv(inp, inbytesleft, &no_read_sub, &comb);
420             break;
421         case 'b':  /* Subscripts */
422             x = yaz_marc8_3_conv(inp, inbytesleft, &no_read_sub, &comb);
423             break;
424         case 'p':  /* Superscripts */
425             x = yaz_marc8_4_conv(inp, inbytesleft, &no_read_sub, &comb);
426             break;
427         case '2':  /* Basic Hebrew */
428             x = yaz_marc8_5_conv(inp, inbytesleft, &no_read_sub, &comb);
429             break;
430         case 'N':  /* Basic Cyrillic */
431         case 'Q':  /* Extended Cyrillic */
432             x = yaz_marc8_6_conv(inp, inbytesleft, &no_read_sub, &comb);
433             break;
434         case '3':  /* Basic Arabic */
435         case '4':  /* Extended Arabic */
436             x = yaz_marc8_7_conv(inp, inbytesleft, &no_read_sub, &comb);
437             break;
438         case 'S':  /* Greek */
439             x = yaz_marc8_8_conv(inp, inbytesleft, &no_read_sub, &comb);
440             break;
441         case '1':  /* Chinese, Japanese, Korean (EACC) */
442             x = yaz_marc8_9_conv(inp, inbytesleft, &no_read_sub, &comb);
443             break;
444         default:
445             *no_read = 0;
446             cd->my_errno = YAZ_ICONV_EILSEQ;
447             return 0;
448         }
449 #if 0
450         printf ("esc mode=%c x=%04lX comb=%d\n", cd->marc8_esc_mode, x, comb);
451 #endif
452         *no_read += no_read_sub;
453
454         if (comb && cd->marc8_comb_x == 0)
455         {
456             size_t tmp_read = 0;
457             unsigned long next_x;
458
459             /* read next char .. */
460             next_x = yaz_read_marc8(cd, inp + *no_read,
461                                     inbytesleft - *no_read, &tmp_read);
462             /* save this x for later .. */
463             cd->marc8_comb_x = x;
464             /* save next read for later .. */
465             cd->marc8_comb_no_read = tmp_read;
466             /* return next x - thereby swap */
467             x = next_x;
468         }
469         return x;
470     }
471 }
472 #endif
473
474 static size_t yaz_write_UTF8 (yaz_iconv_t cd, unsigned long x,
475                               char **outbuf, size_t *outbytesleft,
476                               int last)
477 {
478     unsigned char *outp = (unsigned char *) *outbuf;
479     if (x <= 0x7f && *outbytesleft >= 1)
480     {
481         *outp++ = (unsigned char) x;
482         (*outbytesleft)--;
483     } 
484     else if (x <= 0x7ff && *outbytesleft >= 2)
485     {
486         *outp++ = (unsigned char) ((x >> 6) | 0xc0);
487         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
488         (*outbytesleft) -= 2;
489     }
490     else if (x <= 0xffff && *outbytesleft >= 3)
491     {
492         *outp++ = (unsigned char) ((x >> 12) | 0xe0);
493         *outp++ = (unsigned char) (((x >> 6) & 0x3f) | 0x80);
494         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
495         (*outbytesleft) -= 3;
496     }
497     else if (x <= 0x1fffff && *outbytesleft >= 4)
498     {
499         *outp++ = (unsigned char) ((x >> 18) | 0xf0);
500         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
501         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
502         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
503         (*outbytesleft) -= 4;
504     }
505     else if (x <= 0x3ffffff && *outbytesleft >= 5)
506     {
507         *outp++ = (unsigned char) ((x >> 24) | 0xf8);
508         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
509         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
510         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
511         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
512         (*outbytesleft) -= 5;
513     }
514     else if (*outbytesleft >= 6)
515     {
516         *outp++ = (unsigned char) ((x >> 30) | 0xfc);
517         *outp++ = (unsigned char) (((x >> 24) & 0x3f) | 0x80);
518         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
519         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
520         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
521         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
522         (*outbytesleft) -= 6;
523     }
524     else 
525     {
526         cd->my_errno = YAZ_ICONV_E2BIG;  /* not room for output */
527         return (size_t)(-1);
528     }
529     *outbuf = (char *) outp;
530     return 0;
531 }
532
533
534 static size_t yaz_write_ISO8859_1 (yaz_iconv_t cd, unsigned long x,
535                                    char **outbuf, size_t *outbytesleft,
536                                    int last)
537 {
538     /* list of two char unicode sequence that, when combined, are
539        equivalent to single unicode chars that can be represented in
540        ISO-8859-1/Latin-1.
541        Regular iconv on Linux at least does not seem to convert these,
542        but since MARC-8 to UTF-8 generates these composed sequence
543        we get a better chance of a successful MARC-8 -> ISO-8859-1
544        conversion */
545     static struct {
546         unsigned long x1, x2;
547         unsigned y;
548     } comb[] = {
549         { 'A', 0x0300, 0xc0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
550         { 'A', 0x0301, 0xc1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
551         { 'A', 0x0302, 0xc2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
552         { 'A', 0x0303, 0xc3}, /* LATIN CAPITAL LETTER A WITH TILDE */
553         { 'A', 0x0308, 0xc4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
554         { 'A', 0x030a, 0xc5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
555         /* no need for 0xc6      LATIN CAPITAL LETTER AE */
556         { 'C', 0x0327, 0xc7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
557         { 'E', 0x0300, 0xc8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
558         { 'E', 0x0301, 0xc9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
559         { 'E', 0x0302, 0xca}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
560         { 'E', 0x0308, 0xcb}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
561         { 'I', 0x0300, 0xcc}, /* LATIN CAPITAL LETTER I WITH GRAVE */
562         { 'I', 0x0301, 0xcd}, /* LATIN CAPITAL LETTER I WITH ACUTE */
563         { 'I', 0x0302, 0xce}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
564         { 'I', 0x0308, 0xcf}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
565         { 'N', 0x0303, 0xd1}, /* LATIN CAPITAL LETTER N WITH TILDE */
566         { 'O', 0x0300, 0xd2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
567         { 'O', 0x0301, 0xd3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
568         { 'O', 0x0302, 0xd4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
569         { 'O', 0x0303, 0xd5}, /* LATIN CAPITAL LETTER O WITH TILDE */
570         { 'O', 0x0308, 0xd6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
571         /* omitted:    0xd7      MULTIPLICATION SIGN */
572         /* omitted:    0xd8      LATIN CAPITAL LETTER O WITH STROKE */
573         { 'U', 0x0300, 0xd9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
574         { 'U', 0x0301, 0xda}, /* LATIN CAPITAL LETTER U WITH ACUTE */
575         { 'U', 0x0302, 0xdb}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
576         { 'U', 0x0308, 0xdc}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
577         { 'Y', 0x0301, 0xdd}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
578         /* omitted:    0xde      LATIN CAPITAL LETTER THORN */
579         /* omitted:    0xdf      LATIN SMALL LETTER SHARP S */
580         { 'a', 0x0300, 0xe0}, /* LATIN SMALL LETTER A WITH GRAVE */
581         { 'a', 0x0301, 0xe1}, /* LATIN SMALL LETTER A WITH ACUTE */
582         { 'a', 0x0302, 0xe2}, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
583         { 'a', 0x0303, 0xe3}, /* LATIN SMALL LETTER A WITH TILDE */
584         { 'a', 0x0308, 0xe4}, /* LATIN SMALL LETTER A WITH DIAERESIS */
585         { 'a', 0x030a, 0xe5}, /* LATIN SMALL LETTER A WITH RING ABOVE */
586         /* omitted:    0xe6      LATIN SMALL LETTER AE */
587         { 'c', 0x0327, 0xe7}, /* LATIN SMALL LETTER C WITH CEDILLA */
588         { 'e', 0x0300, 0xe8}, /* LATIN SMALL LETTER E WITH GRAVE */
589         { 'e', 0x0301, 0xe9}, /* LATIN SMALL LETTER E WITH ACUTE */
590         { 'e', 0x0302, 0xea}, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
591         { 'e', 0x0308, 0xeb}, /* LATIN SMALL LETTER E WITH DIAERESIS */
592         { 'i', 0x0300, 0xec}, /* LATIN SMALL LETTER I WITH GRAVE */
593         { 'i', 0x0301, 0xed}, /* LATIN SMALL LETTER I WITH ACUTE */
594         { 'i', 0x0302, 0xee}, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
595         { 'i', 0x0308, 0xef}, /* LATIN SMALL LETTER I WITH DIAERESIS */
596         /* omitted:    0xf0      LATIN SMALL LETTER ETH */
597         { 'n', 0x0303, 0xf1}, /* LATIN SMALL LETTER N WITH TILDE */
598         { 'o', 0x0300, 0xf2}, /* LATIN SMALL LETTER O WITH GRAVE */
599         { 'o', 0x0301, 0xf3}, /* LATIN SMALL LETTER O WITH ACUTE */
600         { 'o', 0x0302, 0xf4}, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
601         { 'o', 0x0303, 0xf5}, /* LATIN SMALL LETTER O WITH TILDE */
602         { 'o', 0x0308, 0xf6}, /* LATIN SMALL LETTER O WITH DIAERESIS */
603         /* omitted:    0xf7      DIVISION SIGN */
604         /* omitted:    0xf8      LATIN SMALL LETTER O WITH STROKE */
605         { 'u', 0x0300, 0xf9}, /* LATIN SMALL LETTER U WITH GRAVE */
606         { 'u', 0x0301, 0xfa}, /* LATIN SMALL LETTER U WITH ACUTE */
607         { 'u', 0x0302, 0xfb}, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
608         { 'u', 0x0308, 0xfc}, /* LATIN SMALL LETTER U WITH DIAERESIS */
609         { 'y', 0x0301, 0xfd}, /* LATIN SMALL LETTER Y WITH ACUTE */
610         /* omitted:    0xfe      LATIN SMALL LETTER THORN */
611         { 'y', 0x0308, 0xff}, /* LATIN SMALL LETTER Y WITH DIAERESIS */
612         
613         { 0, 0, 0}
614     };
615     unsigned char *outp = (unsigned char *) *outbuf;
616
617     if (!last && x > 32 && x < 127 && cd->compose_char == 0)
618     {
619         cd->compose_char = x;
620         return 0;
621     }
622     else if (cd->compose_char)
623     {
624         int i;
625         for (i = 0; comb[i].x1; i++)
626             if (cd->compose_char == comb[i].x1 && x == comb[i].x2)
627             {
628                 x = comb[i].y;
629                 break;
630             }
631         if (!comb[i].x1) 
632         {   /* not found */
633             if (*outbytesleft >= 1)
634             {
635                 *outp++ = (unsigned char) cd->compose_char;
636                 (*outbytesleft)--;
637                 *outbuf = (char *) outp;
638                 if (!last && x > 32 && x < 127)
639                 {
640                     cd->compose_char = x;
641                     return 0;
642                 }
643             }
644             else
645             {
646                 cd->my_errno = YAZ_ICONV_E2BIG;
647                 return (size_t)(-1);
648             }
649         }
650         /* compose_char and old x combined to one new char: x */
651         cd->compose_char = 0;
652     }
653     if (x > 255 || x < 1)
654     {
655         cd->my_errno = YAZ_ICONV_EILSEQ;
656         return (size_t) -1;
657     }
658     else if (*outbytesleft >= 1)
659     {
660         *outp++ = (unsigned char) x;
661         (*outbytesleft)--;
662     }
663     else 
664     {
665         cd->my_errno = YAZ_ICONV_E2BIG;
666         return (size_t)(-1);
667     }
668     *outbuf = (char *) outp;
669     return 0;
670 }
671
672
673 static size_t yaz_write_UCS4 (yaz_iconv_t cd, unsigned long x,
674                               char **outbuf, size_t *outbytesleft,
675                               int last)
676 {
677     unsigned char *outp = (unsigned char *) *outbuf;
678     if (*outbytesleft >= 4)
679     {
680         *outp++ = (unsigned char) (x>>24);
681         *outp++ = (unsigned char) (x>>16);
682         *outp++ = (unsigned char) (x>>8);
683         *outp++ = (unsigned char) x;
684         (*outbytesleft) -= 4;
685     }
686     else
687     {
688         cd->my_errno = YAZ_ICONV_E2BIG;
689         return (size_t)(-1);
690     }
691     *outbuf = (char *) outp;
692     return 0;
693 }
694
695 static size_t yaz_write_UCS4LE (yaz_iconv_t cd, unsigned long x,
696                                 char **outbuf, size_t *outbytesleft,
697                                 int last)
698 {
699     unsigned char *outp = (unsigned char *) *outbuf;
700     if (*outbytesleft >= 4)
701     {
702         *outp++ = (unsigned char) x;
703         *outp++ = (unsigned char) (x>>8);
704         *outp++ = (unsigned char) (x>>16);
705         *outp++ = (unsigned char) (x>>24);
706         (*outbytesleft) -= 4;
707     }
708     else
709     {
710         cd->my_errno = YAZ_ICONV_E2BIG;
711         return (size_t)(-1);
712     }
713     *outbuf = (char *) outp;
714     return 0;
715 }
716
717 #if HAVE_WCHAR_H
718 static size_t yaz_write_wchar_t (yaz_iconv_t cd, unsigned long x,
719                                  char **outbuf, size_t *outbytesleft,
720                                  int last)
721 {
722     unsigned char *outp = (unsigned char *) *outbuf;
723
724     if (*outbytesleft >= sizeof(wchar_t))
725     {
726         wchar_t wch = x;
727         memcpy(outp, &wch, sizeof(wch));
728         outp += sizeof(wch);
729         (*outbytesleft) -= sizeof(wch);
730     }
731     else
732     {
733         cd->my_errno = YAZ_ICONV_E2BIG;
734         return (size_t)(-1);
735     }
736     *outbuf = (char *) outp;
737     return 0;
738 }
739 #endif
740
741 int yaz_iconv_isbuiltin(yaz_iconv_t cd)
742 {
743     return cd->read_handle && cd->write_handle;
744 }
745
746 yaz_iconv_t yaz_iconv_open (const char *tocode, const char *fromcode)
747 {
748     yaz_iconv_t cd = (yaz_iconv_t) xmalloc (sizeof(*cd));
749
750     cd->write_handle = 0;
751     cd->read_handle = 0;
752     cd->init_handle = 0;
753     cd->my_errno = YAZ_ICONV_UNKNOWN;
754     cd->marc8_esc_mode = 'B';
755 #if NEW_COMB
756     cd->comb_offset = cd->comb_size = 0;
757 #else
758     cd->marc8_comb_x = 0;
759 #endif
760     cd->compose_char = 0;
761
762     /* a useful hack: if fromcode has leading @,
763        the library not use YAZ's own conversions .. */
764     if (fromcode[0] == '@')
765         fromcode++;
766     else
767     {
768         if (!yaz_matchstr(fromcode, "UTF8"))
769         {
770             cd->read_handle = yaz_read_UTF8;
771             cd->init_handle = yaz_init_UTF8;
772         }
773         else if (!yaz_matchstr(fromcode, "ISO88591"))
774             cd->read_handle = yaz_read_ISO8859_1;
775         else if (!yaz_matchstr(fromcode, "UCS4"))
776             cd->read_handle = yaz_read_UCS4;
777         else if (!yaz_matchstr(fromcode, "UCS4LE"))
778             cd->read_handle = yaz_read_UCS4LE;
779         else if (!yaz_matchstr(fromcode, "MARC8"))
780             cd->read_handle = yaz_read_marc8;
781 #if HAVE_WCHAR_H
782         else if (!yaz_matchstr(fromcode, "WCHAR_T"))
783             cd->read_handle = yaz_read_wchar_t;
784 #endif
785         
786         if (!yaz_matchstr(tocode, "UTF8"))
787             cd->write_handle = yaz_write_UTF8;
788         else if (!yaz_matchstr(tocode, "ISO88591"))
789             cd->write_handle = yaz_write_ISO8859_1;
790         else if (!yaz_matchstr (tocode, "UCS4"))
791             cd->write_handle = yaz_write_UCS4;
792         else if (!yaz_matchstr(tocode, "UCS4LE"))
793             cd->write_handle = yaz_write_UCS4LE;
794 #if HAVE_WCHAR_H
795         else if (!yaz_matchstr(tocode, "WCHAR_T"))
796             cd->write_handle = yaz_write_wchar_t;
797 #endif
798     }
799 #if HAVE_ICONV_H
800     cd->iconv_cd = 0;
801     if (!cd->read_handle || !cd->write_handle)
802     {
803         cd->iconv_cd = iconv_open (tocode, fromcode);
804         if (cd->iconv_cd == (iconv_t) (-1))
805         {
806             xfree (cd);
807             return 0;
808         }
809     }
810 #else
811     if (!cd->read_handle || !cd->write_handle)
812     {
813         xfree (cd);
814         return 0;
815     }
816 #endif
817     cd->init_flag = 1;
818     return cd;
819 }
820
821 size_t yaz_iconv(yaz_iconv_t cd, char **inbuf, size_t *inbytesleft,
822                  char **outbuf, size_t *outbytesleft)
823 {
824     char *inbuf0;
825     size_t r = 0;
826 #if HAVE_ICONV_H
827     if (cd->iconv_cd)
828     {
829         size_t r =
830             iconv(cd->iconv_cd, inbuf, inbytesleft, outbuf, outbytesleft);
831         if (r == (size_t)(-1))
832         {
833             switch (yaz_errno())
834             {
835             case E2BIG:
836                 cd->my_errno = YAZ_ICONV_E2BIG;
837                 break;
838             case EINVAL:
839                 cd->my_errno = YAZ_ICONV_EINVAL;
840                 break;
841             case EILSEQ:
842                 cd->my_errno = YAZ_ICONV_EILSEQ;
843                 break;
844             default:
845                 cd->my_errno = YAZ_ICONV_UNKNOWN;
846             }
847         }
848         return r;
849     }
850 #endif
851     if (inbuf == 0 || *inbuf == 0)
852     {
853         cd->init_flag = 1;
854         cd->my_errno = YAZ_ICONV_UNKNOWN;
855         return 0;
856     }
857     inbuf0 = *inbuf;
858
859     if (cd->init_flag)
860     {
861         if (cd->init_handle)
862         {
863             size_t no_read;
864             size_t r = (cd->init_handle)(cd, (unsigned char *) *inbuf,
865                                          *inbytesleft, &no_read);
866             if (r)
867             {
868                 if (cd->my_errno == YAZ_ICONV_EINVAL)
869                     return r;
870                 cd->init_flag = 0;
871                 return r;
872             }
873             *inbytesleft -= no_read;
874             *inbuf += no_read;
875         }
876         cd->init_flag = 0;
877         cd->unget_x = 0;
878         cd->no_read_x = 0;
879     }
880     while (1)
881     {
882         unsigned long x;
883         size_t no_read;
884
885         if (*inbytesleft == 0)
886         {
887             r = *inbuf - inbuf0;
888             break;
889         }
890         if (!cd->unget_x)
891         {
892             x = (cd->read_handle)(cd, (unsigned char *) *inbuf, *inbytesleft,
893                                   &no_read);
894             if (no_read == 0)
895             {
896                 r = (size_t)(-1);
897                 break;
898             }
899         }
900         else
901         {
902             x = cd->unget_x;
903             no_read = cd->no_read_x;
904         }
905         if (x)
906         {
907             r = (cd->write_handle)(cd, x, outbuf, outbytesleft,
908                                    (*inbytesleft - no_read) == 0 ? 1 : 0);
909             if (r)
910             {
911                 /* unable to write it. save it because read_handle cannot
912                    rewind .. */
913                 cd->unget_x = x;
914                 cd->no_read_x = no_read;
915                 break;
916             }
917             cd->unget_x = 0;
918         }
919         *inbytesleft -= no_read;
920         (*inbuf) += no_read;
921     }
922     return r;
923 }
924
925 int yaz_iconv_error (yaz_iconv_t cd)
926 {
927     return cd->my_errno;
928 }
929
930 int yaz_iconv_close (yaz_iconv_t cd)
931 {
932 #if HAVE_ICONV_H
933     if (cd->iconv_cd)
934         iconv_close (cd->iconv_cd);
935 #endif
936     xfree (cd);
937     return 0;
938 }
939
940     
941 /*
942  * Local variables:
943  * c-basic-offset: 4
944  * indent-tabs-mode: nil
945  * End:
946  * vim: shiftwidth=4 tabstop=8 expandtab
947  */
948