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