Expanded tabs in all source files. Added vim/emacs local variables
[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.13 2005-06-25 15:46:05 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         cd->comb_offset++;
270         return x;
271     }
272
273     cd->comb_offset = 0;
274     for (cd->comb_size = 0; cd->comb_size < 8; cd->comb_size++)
275     {
276         int comb = 0;
277         x = yaz_read_marc8_comb(cd, inp, inbytesleft, no_read, &comb);
278         if (!comb || !x)
279             break;
280         cd->comb_x[cd->comb_size] = x;
281         cd->comb_no_read[cd->comb_size] = *no_read;
282         inp += *no_read;
283         inbytesleft = inbytesleft - *no_read;
284     }
285     return x;
286 }
287
288 static unsigned long yaz_read_marc8_comb (yaz_iconv_t cd, unsigned char *inp,
289                                           size_t inbytesleft, size_t *no_read,
290                                           int *comb)
291 {
292     *no_read = 0;
293     while(inbytesleft >= 1 && inp[0] == 27)
294     {
295         size_t inbytesleft0 = inbytesleft;
296         inp++;
297         inbytesleft--;
298         while(inbytesleft > 0 && strchr("(,$!", *inp))
299         {
300             inbytesleft--;
301             inp++;
302         }
303         if (inbytesleft <= 0)
304         {
305             *no_read = 0;
306             cd->my_errno = YAZ_ICONV_EINVAL;
307             return 0;
308         }
309         cd->marc8_esc_mode = *inp++;
310         inbytesleft--;
311         (*no_read) += inbytesleft0 - inbytesleft;
312     }
313     if (inbytesleft <= 0)
314         return 0;
315     else
316     {
317         unsigned long x;
318         size_t no_read_sub = 0;
319         *comb = 0;
320
321         switch(cd->marc8_esc_mode)
322         {
323         case 'B':  /* Basic ASCII */
324         case 'E':  /* ANSEL */
325         case 's':  /* ASCII */
326             x = yaz_marc8_1_conv(inp, inbytesleft, &no_read_sub, comb);
327             break;
328         case 'g':  /* Greek */
329             x = yaz_marc8_2_conv(inp, inbytesleft, &no_read_sub, comb);
330             break;
331         case 'b':  /* Subscripts */
332             x = yaz_marc8_3_conv(inp, inbytesleft, &no_read_sub, comb);
333             break;
334         case 'p':  /* Superscripts */
335             x = yaz_marc8_4_conv(inp, inbytesleft, &no_read_sub, comb);
336             break;
337         case '2':  /* Basic Hebrew */
338             x = yaz_marc8_5_conv(inp, inbytesleft, &no_read_sub, comb);
339             break;
340         case 'N':  /* Basic Cyrillic */
341         case 'Q':  /* Extended Cyrillic */
342             x = yaz_marc8_6_conv(inp, inbytesleft, &no_read_sub, comb);
343             break;
344         case '3':  /* Basic Arabic */
345         case '4':  /* Extended Arabic */
346             x = yaz_marc8_7_conv(inp, inbytesleft, &no_read_sub, comb);
347             break;
348         case 'S':  /* Greek */
349             x = yaz_marc8_8_conv(inp, inbytesleft, &no_read_sub, comb);
350             break;
351         case '1':  /* Chinese, Japanese, Korean (EACC) */
352             x = yaz_marc8_9_conv(inp, inbytesleft, &no_read_sub, comb);
353             break;
354         default:
355             *no_read = 0;
356             cd->my_errno = YAZ_ICONV_EILSEQ;
357             return 0;
358         }
359         *no_read += no_read_sub;
360         return x;
361     }
362 }
363 #else
364 static unsigned long yaz_read_marc8 (yaz_iconv_t cd, unsigned char *inp,
365                                      size_t inbytesleft, size_t *no_read)
366 {
367     if (cd->marc8_comb_x)
368     {
369         unsigned long x = cd->marc8_comb_x;
370         *no_read = cd->marc8_comb_no_read;
371         cd->marc8_comb_x = 0;
372         return x;
373     }
374     *no_read = 0;
375     while(inbytesleft >= 1 && inp[0] == 27)
376     {
377         size_t inbytesleft0 = inbytesleft;
378         inp++;
379         inbytesleft--;
380         while(inbytesleft > 0 && strchr("(,$!", *inp))
381         {
382             inbytesleft--;
383             inp++;
384         }
385         if (inbytesleft <= 0)
386         {
387             *no_read = 0;
388             cd->my_errno = YAZ_ICONV_EINVAL;
389             return 0;
390         }
391         cd->marc8_esc_mode = *inp++;
392         inbytesleft--;
393         (*no_read) += inbytesleft0 - inbytesleft;
394     }
395     if (inbytesleft <= 0)
396         return 0;
397     else
398     {
399         unsigned long x;
400         int comb = 0;
401         size_t no_read_sub = 0;
402
403         switch(cd->marc8_esc_mode)
404         {
405         case 'B':  /* Basic ASCII */
406         case 'E':  /* ANSEL */
407         case 's':  /* ASCII */
408             x = yaz_marc8_1_conv(inp, inbytesleft, &no_read_sub, &comb);
409             break;
410         case 'g':  /* Greek */
411             x = yaz_marc8_2_conv(inp, inbytesleft, &no_read_sub, &comb);
412             break;
413         case 'b':  /* Subscripts */
414             x = yaz_marc8_3_conv(inp, inbytesleft, &no_read_sub, &comb);
415             break;
416         case 'p':  /* Superscripts */
417             x = yaz_marc8_4_conv(inp, inbytesleft, &no_read_sub, &comb);
418             break;
419         case '2':  /* Basic Hebrew */
420             x = yaz_marc8_5_conv(inp, inbytesleft, &no_read_sub, &comb);
421             break;
422         case 'N':  /* Basic Cyrillic */
423         case 'Q':  /* Extended Cyrillic */
424             x = yaz_marc8_6_conv(inp, inbytesleft, &no_read_sub, &comb);
425             break;
426         case '3':  /* Basic Arabic */
427         case '4':  /* Extended Arabic */
428             x = yaz_marc8_7_conv(inp, inbytesleft, &no_read_sub, &comb);
429             break;
430         case 'S':  /* Greek */
431             x = yaz_marc8_8_conv(inp, inbytesleft, &no_read_sub, &comb);
432             break;
433         case '1':  /* Chinese, Japanese, Korean (EACC) */
434             x = yaz_marc8_9_conv(inp, inbytesleft, &no_read_sub, &comb);
435             break;
436         default:
437             *no_read = 0;
438             cd->my_errno = YAZ_ICONV_EILSEQ;
439             return 0;
440         }
441 #if 0
442         printf ("esc mode=%c x=%04lX comb=%d\n", cd->marc8_esc_mode, x, comb);
443 #endif
444         *no_read += no_read_sub;
445
446         if (comb && cd->marc8_comb_x == 0)
447         {
448             size_t tmp_read = 0;
449             unsigned long next_x;
450
451             /* read next char .. */
452             next_x = yaz_read_marc8(cd, inp + *no_read,
453                                     inbytesleft - *no_read, &tmp_read);
454             /* save this x for later .. */
455             cd->marc8_comb_x = x;
456             /* save next read for later .. */
457             cd->marc8_comb_no_read = tmp_read;
458             /* return next x - thereby swap */
459             x = next_x;
460         }
461         return x;
462     }
463 }
464 #endif
465
466 static size_t yaz_write_UTF8 (yaz_iconv_t cd, unsigned long x,
467                               char **outbuf, size_t *outbytesleft,
468                               int last)
469 {
470     unsigned char *outp = (unsigned char *) *outbuf;
471     if (x <= 0x7f && *outbytesleft >= 1)
472     {
473         *outp++ = (unsigned char) x;
474         (*outbytesleft)--;
475     } 
476     else if (x <= 0x7ff && *outbytesleft >= 2)
477     {
478         *outp++ = (unsigned char) ((x >> 6) | 0xc0);
479         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
480         (*outbytesleft) -= 2;
481     }
482     else if (x <= 0xffff && *outbytesleft >= 3)
483     {
484         *outp++ = (unsigned char) ((x >> 12) | 0xe0);
485         *outp++ = (unsigned char) (((x >> 6) & 0x3f) | 0x80);
486         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
487         (*outbytesleft) -= 3;
488     }
489     else if (x <= 0x1fffff && *outbytesleft >= 4)
490     {
491         *outp++ = (unsigned char) ((x >> 18) | 0xf0);
492         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
493         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
494         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
495         (*outbytesleft) -= 4;
496     }
497     else if (x <= 0x3ffffff && *outbytesleft >= 5)
498     {
499         *outp++ = (unsigned char) ((x >> 24) | 0xf8);
500         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
501         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
502         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
503         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
504         (*outbytesleft) -= 5;
505     }
506     else if (*outbytesleft >= 6)
507     {
508         *outp++ = (unsigned char) ((x >> 30) | 0xfc);
509         *outp++ = (unsigned char) (((x >> 24) & 0x3f) | 0x80);
510         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
511         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
512         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
513         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
514         (*outbytesleft) -= 6;
515     }
516     else 
517     {
518         cd->my_errno = YAZ_ICONV_E2BIG;  /* not room for output */
519         return (size_t)(-1);
520     }
521     *outbuf = (char *) outp;
522     return 0;
523 }
524
525
526 static size_t yaz_write_ISO8859_1 (yaz_iconv_t cd, unsigned long x,
527                                    char **outbuf, size_t *outbytesleft,
528                                    int last)
529 {
530     /* list of two char unicode sequence that, when combined, are
531        equivalent to single unicode chars that can be represented in
532        ISO-8859-1/Latin-1.
533        Regular iconv on Linux at least does not seem to convert these,
534        but since MARC-8 to UTF-8 generates these composed sequence
535        we get a better chance of a successful MARC-8 -> ISO-8859-1
536        conversion */
537     static struct {
538         unsigned long x1, x2;
539         unsigned y;
540     } comb[] = {
541         { 'A', 0x0300, 0xc0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
542         { 'A', 0x0301, 0xc1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
543         { 'A', 0x0302, 0xc2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
544         { 'A', 0x0303, 0xc3}, /* LATIN CAPITAL LETTER A WITH TILDE */
545         { 'A', 0x0308, 0xc4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
546         { 'A', 0x030a, 0xc5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
547         /* no need for 0xc6      LATIN CAPITAL LETTER AE */
548         { 'C', 0x0327, 0xc7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
549         { 'E', 0x0300, 0xc8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
550         { 'E', 0x0301, 0xc9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
551         { 'E', 0x0302, 0xca}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
552         { 'E', 0x0308, 0xcb}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
553         { 'I', 0x0300, 0xcc}, /* LATIN CAPITAL LETTER I WITH GRAVE */
554         { 'I', 0x0301, 0xcd}, /* LATIN CAPITAL LETTER I WITH ACUTE */
555         { 'I', 0x0302, 0xce}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
556         { 'I', 0x0308, 0xcf}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
557         { 'N', 0x0303, 0xd1}, /* LATIN CAPITAL LETTER N WITH TILDE */
558         { 'O', 0x0300, 0xd2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
559         { 'O', 0x0301, 0xd3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
560         { 'O', 0x0302, 0xd4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
561         { 'O', 0x0303, 0xd5}, /* LATIN CAPITAL LETTER O WITH TILDE */
562         { 'O', 0x0308, 0xd6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
563         /* omitted:    0xd7      MULTIPLICATION SIGN */
564         /* omitted:    0xd8      LATIN CAPITAL LETTER O WITH STROKE */
565         { 'U', 0x0300, 0xd9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
566         { 'U', 0x0301, 0xda}, /* LATIN CAPITAL LETTER U WITH ACUTE */
567         { 'U', 0x0302, 0xdb}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
568         { 'U', 0x0308, 0xdc}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
569         { 'Y', 0x0301, 0xdd}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
570         /* omitted:    0xde      LATIN CAPITAL LETTER THORN */
571         /* omitted:    0xdf      LATIN SMALL LETTER SHARP S */
572         { 'a', 0x0300, 0xe0}, /* LATIN SMALL LETTER A WITH GRAVE */
573         { 'a', 0x0301, 0xe1}, /* LATIN SMALL LETTER A WITH ACUTE */
574         { 'a', 0x0302, 0xe2}, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
575         { 'a', 0x0303, 0xe3}, /* LATIN SMALL LETTER A WITH TILDE */
576         { 'a', 0x0308, 0xe4}, /* LATIN SMALL LETTER A WITH DIAERESIS */
577         { 'a', 0x030a, 0xe5}, /* LATIN SMALL LETTER A WITH RING ABOVE */
578         /* omitted:    0xe6      LATIN SMALL LETTER AE */
579         { 'c', 0x0327, 0xe7}, /* LATIN SMALL LETTER C WITH CEDILLA */
580         { 'e', 0x0300, 0xe8}, /* LATIN SMALL LETTER E WITH GRAVE */
581         { 'e', 0x0301, 0xe9}, /* LATIN SMALL LETTER E WITH ACUTE */
582         { 'e', 0x0302, 0xea}, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
583         { 'e', 0x0308, 0xeb}, /* LATIN SMALL LETTER E WITH DIAERESIS */
584         { 'i', 0x0300, 0xec}, /* LATIN SMALL LETTER I WITH GRAVE */
585         { 'i', 0x0301, 0xed}, /* LATIN SMALL LETTER I WITH ACUTE */
586         { 'i', 0x0302, 0xee}, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
587         { 'i', 0x0308, 0xef}, /* LATIN SMALL LETTER I WITH DIAERESIS */
588         /* omitted:    0xf0      LATIN SMALL LETTER ETH */
589         { 'n', 0x0303, 0xf1}, /* LATIN SMALL LETTER N WITH TILDE */
590         { 'o', 0x0300, 0xf2}, /* LATIN SMALL LETTER O WITH GRAVE */
591         { 'o', 0x0301, 0xf3}, /* LATIN SMALL LETTER O WITH ACUTE */
592         { 'o', 0x0302, 0xf4}, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
593         { 'o', 0x0303, 0xf5}, /* LATIN SMALL LETTER O WITH TILDE */
594         { 'o', 0x0308, 0xf6}, /* LATIN SMALL LETTER O WITH DIAERESIS */
595         /* omitted:    0xf7      DIVISION SIGN */
596         /* omitted:    0xf8      LATIN SMALL LETTER O WITH STROKE */
597         { 'u', 0x0300, 0xf9}, /* LATIN SMALL LETTER U WITH GRAVE */
598         { 'u', 0x0301, 0xfa}, /* LATIN SMALL LETTER U WITH ACUTE */
599         { 'u', 0x0302, 0xfb}, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
600         { 'u', 0x0308, 0xfc}, /* LATIN SMALL LETTER U WITH DIAERESIS */
601         { 'y', 0x0301, 0xfd}, /* LATIN SMALL LETTER Y WITH ACUTE */
602         /* omitted:    0xfe      LATIN SMALL LETTER THORN */
603         { 'y', 0x0308, 0xff}, /* LATIN SMALL LETTER Y WITH DIAERESIS */
604         
605         { 0, 0, 0}
606     };
607     unsigned char *outp = (unsigned char *) *outbuf;
608
609     if (!last && x > 32 && x < 127 && cd->compose_char == 0)
610     {
611         cd->compose_char = x;
612         return 0;
613     }
614     else if (cd->compose_char)
615     {
616         int i;
617         for (i = 0; comb[i].x1; i++)
618             if (cd->compose_char == comb[i].x1 && x == comb[i].x2)
619             {
620                 x = comb[i].y;
621                 break;
622             }
623         if (!comb[i].x1) 
624         {   /* not found */
625             if (*outbytesleft >= 1)
626             {
627                 *outp++ = (unsigned char) cd->compose_char;
628                 (*outbytesleft)--;
629                 *outbuf = (char *) outp;
630                 if (!last && x > 32 && x < 127)
631                 {
632                     cd->compose_char = x;
633                     return 0;
634                 }
635             }
636             else
637             {
638                 cd->my_errno = YAZ_ICONV_E2BIG;
639                 return (size_t)(-1);
640             }
641         }
642         /* compose_char and old x combined to one new char: x */
643         cd->compose_char = 0;
644     }
645     if (x > 255 || x < 1)
646     {
647         cd->my_errno = YAZ_ICONV_EILSEQ;
648         return (size_t) -1;
649     }
650     else if (*outbytesleft >= 1)
651     {
652         *outp++ = (unsigned char) x;
653         (*outbytesleft)--;
654     }
655     else 
656     {
657         cd->my_errno = YAZ_ICONV_E2BIG;
658         return (size_t)(-1);
659     }
660     *outbuf = (char *) outp;
661     return 0;
662 }
663
664
665 static size_t yaz_write_UCS4 (yaz_iconv_t cd, unsigned long x,
666                               char **outbuf, size_t *outbytesleft,
667                               int last)
668 {
669     unsigned char *outp = (unsigned char *) *outbuf;
670     if (*outbytesleft >= 4)
671     {
672         *outp++ = (unsigned char) (x>>24);
673         *outp++ = (unsigned char) (x>>16);
674         *outp++ = (unsigned char) (x>>8);
675         *outp++ = (unsigned char) x;
676         (*outbytesleft) -= 4;
677     }
678     else
679     {
680         cd->my_errno = YAZ_ICONV_E2BIG;
681         return (size_t)(-1);
682     }
683     *outbuf = (char *) outp;
684     return 0;
685 }
686
687 static size_t yaz_write_UCS4LE (yaz_iconv_t cd, unsigned long x,
688                                 char **outbuf, size_t *outbytesleft,
689                                 int last)
690 {
691     unsigned char *outp = (unsigned char *) *outbuf;
692     if (*outbytesleft >= 4)
693     {
694         *outp++ = (unsigned char) x;
695         *outp++ = (unsigned char) (x>>8);
696         *outp++ = (unsigned char) (x>>16);
697         *outp++ = (unsigned char) (x>>24);
698         (*outbytesleft) -= 4;
699     }
700     else
701     {
702         cd->my_errno = YAZ_ICONV_E2BIG;
703         return (size_t)(-1);
704     }
705     *outbuf = (char *) outp;
706     return 0;
707 }
708
709 #if HAVE_WCHAR_H
710 static size_t yaz_write_wchar_t (yaz_iconv_t cd, unsigned long x,
711                                  char **outbuf, size_t *outbytesleft,
712                                  int last)
713 {
714     unsigned char *outp = (unsigned char *) *outbuf;
715
716     if (*outbytesleft >= sizeof(wchar_t))
717     {
718         wchar_t wch = x;
719         memcpy(outp, &wch, sizeof(wch));
720         outp += sizeof(wch);
721         (*outbytesleft) -= sizeof(wch);
722     }
723     else
724     {
725         cd->my_errno = YAZ_ICONV_E2BIG;
726         return (size_t)(-1);
727     }
728     *outbuf = (char *) outp;
729     return 0;
730 }
731 #endif
732
733 int yaz_iconv_isbuiltin(yaz_iconv_t cd)
734 {
735     return cd->read_handle && cd->write_handle;
736 }
737
738 yaz_iconv_t yaz_iconv_open (const char *tocode, const char *fromcode)
739 {
740     yaz_iconv_t cd = (yaz_iconv_t) xmalloc (sizeof(*cd));
741
742     cd->write_handle = 0;
743     cd->read_handle = 0;
744     cd->init_handle = 0;
745     cd->my_errno = YAZ_ICONV_UNKNOWN;
746     cd->marc8_esc_mode = 'B';
747 #if NEW_COMB
748     cd->comb_offset = cd->comb_size = 0;
749 #else
750     cd->marc8_comb_x = 0;
751 #endif
752     cd->compose_char = 0;
753
754     /* a useful hack: if fromcode has leading @,
755        the library not use YAZ's own conversions .. */
756     if (fromcode[0] == '@')
757         fromcode++;
758     else
759     {
760         if (!yaz_matchstr(fromcode, "UTF8"))
761         {
762             cd->read_handle = yaz_read_UTF8;
763             cd->init_handle = yaz_init_UTF8;
764         }
765         else if (!yaz_matchstr(fromcode, "ISO88591"))
766             cd->read_handle = yaz_read_ISO8859_1;
767         else if (!yaz_matchstr(fromcode, "UCS4"))
768             cd->read_handle = yaz_read_UCS4;
769         else if (!yaz_matchstr(fromcode, "UCS4LE"))
770             cd->read_handle = yaz_read_UCS4LE;
771         else if (!yaz_matchstr(fromcode, "MARC8"))
772             cd->read_handle = yaz_read_marc8;
773 #if HAVE_WCHAR_H
774         else if (!yaz_matchstr(fromcode, "WCHAR_T"))
775             cd->read_handle = yaz_read_wchar_t;
776 #endif
777         
778         if (!yaz_matchstr(tocode, "UTF8"))
779             cd->write_handle = yaz_write_UTF8;
780         else if (!yaz_matchstr(tocode, "ISO88591"))
781             cd->write_handle = yaz_write_ISO8859_1;
782         else if (!yaz_matchstr (tocode, "UCS4"))
783             cd->write_handle = yaz_write_UCS4;
784         else if (!yaz_matchstr(tocode, "UCS4LE"))
785             cd->write_handle = yaz_write_UCS4LE;
786 #if HAVE_WCHAR_H
787         else if (!yaz_matchstr(tocode, "WCHAR_T"))
788             cd->write_handle = yaz_write_wchar_t;
789 #endif
790     }
791 #if HAVE_ICONV_H
792     cd->iconv_cd = 0;
793     if (!cd->read_handle || !cd->write_handle)
794     {
795         cd->iconv_cd = iconv_open (tocode, fromcode);
796         if (cd->iconv_cd == (iconv_t) (-1))
797         {
798             xfree (cd);
799             return 0;
800         }
801     }
802 #else
803     if (!cd->read_handle || !cd->write_handle)
804     {
805         xfree (cd);
806         return 0;
807     }
808 #endif
809     cd->init_flag = 1;
810     return cd;
811 }
812
813 size_t yaz_iconv(yaz_iconv_t cd, char **inbuf, size_t *inbytesleft,
814                  char **outbuf, size_t *outbytesleft)
815 {
816     char *inbuf0;
817     size_t r = 0;
818 #if HAVE_ICONV_H
819     if (cd->iconv_cd)
820     {
821         size_t r =
822             iconv(cd->iconv_cd, inbuf, inbytesleft, outbuf, outbytesleft);
823         if (r == (size_t)(-1))
824         {
825             switch (yaz_errno())
826             {
827             case E2BIG:
828                 cd->my_errno = YAZ_ICONV_E2BIG;
829                 break;
830             case EINVAL:
831                 cd->my_errno = YAZ_ICONV_EINVAL;
832                 break;
833             case EILSEQ:
834                 cd->my_errno = YAZ_ICONV_EILSEQ;
835                 break;
836             default:
837                 cd->my_errno = YAZ_ICONV_UNKNOWN;
838             }
839         }
840         return r;
841     }
842 #endif
843     if (inbuf == 0 || *inbuf == 0)
844     {
845         cd->init_flag = 1;
846         cd->my_errno = YAZ_ICONV_UNKNOWN;
847         return 0;
848     }
849     inbuf0 = *inbuf;
850
851     if (cd->init_flag)
852     {
853         if (cd->init_handle)
854         {
855             size_t no_read;
856             size_t r = (cd->init_handle)(cd, (unsigned char *) *inbuf,
857                                          *inbytesleft, &no_read);
858             if (r)
859             {
860                 if (cd->my_errno == YAZ_ICONV_EINVAL)
861                     return r;
862                 cd->init_flag = 0;
863                 return r;
864             }
865             *inbytesleft -= no_read;
866             *inbuf += no_read;
867         }
868         cd->init_flag = 0;
869         cd->unget_x = 0;
870         cd->no_read_x = 0;
871     }
872     while (1)
873     {
874         unsigned long x;
875         size_t no_read;
876
877         if (*inbytesleft == 0)
878         {
879             r = *inbuf - inbuf0;
880             break;
881         }
882         if (!cd->unget_x)
883         {
884             x = (cd->read_handle)(cd, (unsigned char *) *inbuf, *inbytesleft,
885                                   &no_read);
886             if (no_read == 0)
887             {
888                 r = (size_t)(-1);
889                 break;
890             }
891         }
892         else
893         {
894             x = cd->unget_x;
895             no_read = cd->no_read_x;
896         }
897         if (x)
898         {
899             r = (cd->write_handle)(cd, x, outbuf, outbytesleft,
900                                    (*inbytesleft - no_read) == 0 ? 1 : 0);
901             if (r)
902             {
903                 /* unable to write it. save it because read_handle cannot
904                    rewind .. */
905                 cd->unget_x = x;
906                 cd->no_read_x = no_read;
907                 break;
908             }
909             cd->unget_x = 0;
910         }
911         *inbytesleft -= no_read;
912         (*inbuf) += no_read;
913     }
914     return r;
915 }
916
917 int yaz_iconv_error (yaz_iconv_t cd)
918 {
919     return cd->my_errno;
920 }
921
922 int yaz_iconv_close (yaz_iconv_t cd)
923 {
924 #if HAVE_ICONV_H
925     if (cd->iconv_cd)
926         iconv_close (cd->iconv_cd);
927 #endif
928     xfree (cd);
929     return 0;
930 }
931
932     
933 /*
934  * Local variables:
935  * c-basic-offset: 4
936  * indent-tabs-mode: nil
937  * End:
938  * vim: shiftwidth=4 tabstop=8 expandtab
939  */
940