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