25af4ccb4d6681ba30ebe0181571bc6e21bfd76b
[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.18 2006-03-25 14:41:53 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[2] & 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
380     if (x <= 0x7f && *outbytesleft >= 1)
381     {
382         *outp++ = (unsigned char) x;
383         (*outbytesleft)--;
384     } 
385     else if (x <= 0x7ff && *outbytesleft >= 2)
386     {
387         *outp++ = (unsigned char) ((x >> 6) | 0xc0);
388         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
389         (*outbytesleft) -= 2;
390     }
391     else if (x <= 0xffff && *outbytesleft >= 3)
392     {
393         *outp++ = (unsigned char) ((x >> 12) | 0xe0);
394         *outp++ = (unsigned char) (((x >> 6) & 0x3f) | 0x80);
395         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
396         (*outbytesleft) -= 3;
397     }
398     else if (x <= 0x1fffff && *outbytesleft >= 4)
399     {
400         *outp++ = (unsigned char) ((x >> 18) | 0xf0);
401         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
402         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
403         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
404         (*outbytesleft) -= 4;
405     }
406     else if (x <= 0x3ffffff && *outbytesleft >= 5)
407     {
408         *outp++ = (unsigned char) ((x >> 24) | 0xf8);
409         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
410         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
411         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
412         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
413         (*outbytesleft) -= 5;
414     }
415     else if (*outbytesleft >= 6)
416     {
417         *outp++ = (unsigned char) ((x >> 30) | 0xfc);
418         *outp++ = (unsigned char) (((x >> 24) & 0x3f) | 0x80);
419         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
420         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
421         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
422         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
423         (*outbytesleft) -= 6;
424     }
425     else 
426     {
427         cd->my_errno = YAZ_ICONV_E2BIG;  /* not room for output */
428         return (size_t)(-1);
429     }
430     *outbuf = (char *) outp;
431     return 0;
432 }
433
434
435 static size_t yaz_write_ISO8859_1 (yaz_iconv_t cd, unsigned long x,
436                                    char **outbuf, size_t *outbytesleft,
437                                    int last)
438 {
439     /* list of two char unicode sequence that, when combined, are
440        equivalent to single unicode chars that can be represented in
441        ISO-8859-1/Latin-1.
442        Regular iconv on Linux at least does not seem to convert these,
443        but since MARC-8 to UTF-8 generates these composed sequence
444        we get a better chance of a successful MARC-8 -> ISO-8859-1
445        conversion */
446     static struct {
447         unsigned long x1, x2;
448         unsigned y;
449     } comb[] = {
450         { 'A', 0x0300, 0xc0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
451         { 'A', 0x0301, 0xc1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
452         { 'A', 0x0302, 0xc2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
453         { 'A', 0x0303, 0xc3}, /* LATIN CAPITAL LETTER A WITH TILDE */
454         { 'A', 0x0308, 0xc4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
455         { 'A', 0x030a, 0xc5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
456         /* no need for 0xc6      LATIN CAPITAL LETTER AE */
457         { 'C', 0x0327, 0xc7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
458         { 'E', 0x0300, 0xc8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
459         { 'E', 0x0301, 0xc9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
460         { 'E', 0x0302, 0xca}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
461         { 'E', 0x0308, 0xcb}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
462         { 'I', 0x0300, 0xcc}, /* LATIN CAPITAL LETTER I WITH GRAVE */
463         { 'I', 0x0301, 0xcd}, /* LATIN CAPITAL LETTER I WITH ACUTE */
464         { 'I', 0x0302, 0xce}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
465         { 'I', 0x0308, 0xcf}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
466         { 'N', 0x0303, 0xd1}, /* LATIN CAPITAL LETTER N WITH TILDE */
467         { 'O', 0x0300, 0xd2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
468         { 'O', 0x0301, 0xd3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
469         { 'O', 0x0302, 0xd4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
470         { 'O', 0x0303, 0xd5}, /* LATIN CAPITAL LETTER O WITH TILDE */
471         { 'O', 0x0308, 0xd6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
472         /* omitted:    0xd7      MULTIPLICATION SIGN */
473         /* omitted:    0xd8      LATIN CAPITAL LETTER O WITH STROKE */
474         { 'U', 0x0300, 0xd9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
475         { 'U', 0x0301, 0xda}, /* LATIN CAPITAL LETTER U WITH ACUTE */
476         { 'U', 0x0302, 0xdb}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
477         { 'U', 0x0308, 0xdc}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
478         { 'Y', 0x0301, 0xdd}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
479         /* omitted:    0xde      LATIN CAPITAL LETTER THORN */
480         /* omitted:    0xdf      LATIN SMALL LETTER SHARP S */
481         { 'a', 0x0300, 0xe0}, /* LATIN SMALL LETTER A WITH GRAVE */
482         { 'a', 0x0301, 0xe1}, /* LATIN SMALL LETTER A WITH ACUTE */
483         { 'a', 0x0302, 0xe2}, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
484         { 'a', 0x0303, 0xe3}, /* LATIN SMALL LETTER A WITH TILDE */
485         { 'a', 0x0308, 0xe4}, /* LATIN SMALL LETTER A WITH DIAERESIS */
486         { 'a', 0x030a, 0xe5}, /* LATIN SMALL LETTER A WITH RING ABOVE */
487         /* omitted:    0xe6      LATIN SMALL LETTER AE */
488         { 'c', 0x0327, 0xe7}, /* LATIN SMALL LETTER C WITH CEDILLA */
489         { 'e', 0x0300, 0xe8}, /* LATIN SMALL LETTER E WITH GRAVE */
490         { 'e', 0x0301, 0xe9}, /* LATIN SMALL LETTER E WITH ACUTE */
491         { 'e', 0x0302, 0xea}, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
492         { 'e', 0x0308, 0xeb}, /* LATIN SMALL LETTER E WITH DIAERESIS */
493         { 'i', 0x0300, 0xec}, /* LATIN SMALL LETTER I WITH GRAVE */
494         { 'i', 0x0301, 0xed}, /* LATIN SMALL LETTER I WITH ACUTE */
495         { 'i', 0x0302, 0xee}, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
496         { 'i', 0x0308, 0xef}, /* LATIN SMALL LETTER I WITH DIAERESIS */
497         /* omitted:    0xf0      LATIN SMALL LETTER ETH */
498         { 'n', 0x0303, 0xf1}, /* LATIN SMALL LETTER N WITH TILDE */
499         { 'o', 0x0300, 0xf2}, /* LATIN SMALL LETTER O WITH GRAVE */
500         { 'o', 0x0301, 0xf3}, /* LATIN SMALL LETTER O WITH ACUTE */
501         { 'o', 0x0302, 0xf4}, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
502         { 'o', 0x0303, 0xf5}, /* LATIN SMALL LETTER O WITH TILDE */
503         { 'o', 0x0308, 0xf6}, /* LATIN SMALL LETTER O WITH DIAERESIS */
504         /* omitted:    0xf7      DIVISION SIGN */
505         /* omitted:    0xf8      LATIN SMALL LETTER O WITH STROKE */
506         { 'u', 0x0300, 0xf9}, /* LATIN SMALL LETTER U WITH GRAVE */
507         { 'u', 0x0301, 0xfa}, /* LATIN SMALL LETTER U WITH ACUTE */
508         { 'u', 0x0302, 0xfb}, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
509         { 'u', 0x0308, 0xfc}, /* LATIN SMALL LETTER U WITH DIAERESIS */
510         { 'y', 0x0301, 0xfd}, /* LATIN SMALL LETTER Y WITH ACUTE */
511         /* omitted:    0xfe      LATIN SMALL LETTER THORN */
512         { 'y', 0x0308, 0xff}, /* LATIN SMALL LETTER Y WITH DIAERESIS */
513         
514         { 0, 0, 0}
515     };
516     unsigned char *outp = (unsigned char *) *outbuf;
517
518     if (cd->compose_char)
519     {
520         int i;
521         for (i = 0; comb[i].x1; i++)
522             if (cd->compose_char == comb[i].x1 && x == comb[i].x2)
523             {
524                 x = comb[i].y;
525                 break;
526             }
527         if (*outbytesleft < 1)
528         {  /* no room. Retain compose_char and bail out */
529             cd->my_errno = YAZ_ICONV_E2BIG;
530             return (size_t)(-1);
531         }
532         if (!comb[i].x1) 
533         {   /* not found. Just write compose_char */
534             *outp++ = (unsigned char) cd->compose_char;
535             (*outbytesleft)--;
536             *outbuf = (char *) outp;
537         }
538         /* compose_char used so reset it. x now holds current char */
539         cd->compose_char = 0;
540     }
541
542     if (!last && x > 32 && x < 127 && cd->compose_char == 0)
543     {
544         cd->compose_char = x;
545         return 0;
546     }
547     else if (x > 255 || x < 1)
548     {
549         cd->my_errno = YAZ_ICONV_EILSEQ;
550         return (size_t) -1;
551     }
552     else if (*outbytesleft < 1)
553     {
554         cd->my_errno = YAZ_ICONV_E2BIG;
555         return (size_t)(-1);
556     }
557     *outp++ = (unsigned char) x;
558     (*outbytesleft)--;
559     *outbuf = (char *) outp;
560     return 0;
561 }
562
563
564 static size_t yaz_write_UCS4 (yaz_iconv_t cd, unsigned long x,
565                               char **outbuf, size_t *outbytesleft,
566                               int last)
567 {
568     unsigned char *outp = (unsigned char *) *outbuf;
569     if (*outbytesleft >= 4)
570     {
571         *outp++ = (unsigned char) (x>>24);
572         *outp++ = (unsigned char) (x>>16);
573         *outp++ = (unsigned char) (x>>8);
574         *outp++ = (unsigned char) x;
575         (*outbytesleft) -= 4;
576     }
577     else
578     {
579         cd->my_errno = YAZ_ICONV_E2BIG;
580         return (size_t)(-1);
581     }
582     *outbuf = (char *) outp;
583     return 0;
584 }
585
586 static size_t yaz_write_UCS4LE (yaz_iconv_t cd, unsigned long x,
587                                 char **outbuf, size_t *outbytesleft,
588                                 int last)
589 {
590     unsigned char *outp = (unsigned char *) *outbuf;
591     if (*outbytesleft >= 4)
592     {
593         *outp++ = (unsigned char) x;
594         *outp++ = (unsigned char) (x>>8);
595         *outp++ = (unsigned char) (x>>16);
596         *outp++ = (unsigned char) (x>>24);
597         (*outbytesleft) -= 4;
598     }
599     else
600     {
601         cd->my_errno = YAZ_ICONV_E2BIG;
602         return (size_t)(-1);
603     }
604     *outbuf = (char *) outp;
605     return 0;
606 }
607
608 #if HAVE_WCHAR_H
609 static size_t yaz_write_wchar_t (yaz_iconv_t cd, unsigned long x,
610                                  char **outbuf, size_t *outbytesleft,
611                                  int last)
612 {
613     unsigned char *outp = (unsigned char *) *outbuf;
614
615     if (*outbytesleft >= sizeof(wchar_t))
616     {
617         wchar_t wch = x;
618         memcpy(outp, &wch, sizeof(wch));
619         outp += sizeof(wch);
620         (*outbytesleft) -= sizeof(wch);
621     }
622     else
623     {
624         cd->my_errno = YAZ_ICONV_E2BIG;
625         return (size_t)(-1);
626     }
627     *outbuf = (char *) outp;
628     return 0;
629 }
630 #endif
631
632 int yaz_iconv_isbuiltin(yaz_iconv_t cd)
633 {
634     return cd->read_handle && cd->write_handle;
635 }
636
637 yaz_iconv_t yaz_iconv_open (const char *tocode, const char *fromcode)
638 {
639     yaz_iconv_t cd = (yaz_iconv_t) xmalloc (sizeof(*cd));
640
641     cd->write_handle = 0;
642     cd->read_handle = 0;
643     cd->init_handle = 0;
644     cd->my_errno = YAZ_ICONV_UNKNOWN;
645     cd->marc8_esc_mode = 'B';
646     cd->comb_offset = cd->comb_size = 0;
647     cd->compose_char = 0;
648
649     /* a useful hack: if fromcode has leading @,
650        the library not use YAZ's own conversions .. */
651     if (fromcode[0] == '@')
652         fromcode++;
653     else
654     {
655         if (!yaz_matchstr(fromcode, "UTF8"))
656         {
657             cd->read_handle = yaz_read_UTF8;
658             cd->init_handle = yaz_init_UTF8;
659         }
660         else if (!yaz_matchstr(fromcode, "ISO88591"))
661             cd->read_handle = yaz_read_ISO8859_1;
662         else if (!yaz_matchstr(fromcode, "UCS4"))
663             cd->read_handle = yaz_read_UCS4;
664         else if (!yaz_matchstr(fromcode, "UCS4LE"))
665             cd->read_handle = yaz_read_UCS4LE;
666         else if (!yaz_matchstr(fromcode, "MARC8"))
667             cd->read_handle = yaz_read_marc8;
668 #if HAVE_WCHAR_H
669         else if (!yaz_matchstr(fromcode, "WCHAR_T"))
670             cd->read_handle = yaz_read_wchar_t;
671 #endif
672         
673         if (!yaz_matchstr(tocode, "UTF8"))
674             cd->write_handle = yaz_write_UTF8;
675         else if (!yaz_matchstr(tocode, "ISO88591"))
676             cd->write_handle = yaz_write_ISO8859_1;
677         else if (!yaz_matchstr (tocode, "UCS4"))
678             cd->write_handle = yaz_write_UCS4;
679         else if (!yaz_matchstr(tocode, "UCS4LE"))
680             cd->write_handle = yaz_write_UCS4LE;
681 #if HAVE_WCHAR_H
682         else if (!yaz_matchstr(tocode, "WCHAR_T"))
683             cd->write_handle = yaz_write_wchar_t;
684 #endif
685     }
686 #if HAVE_ICONV_H
687     cd->iconv_cd = 0;
688     if (!cd->read_handle || !cd->write_handle)
689     {
690         cd->iconv_cd = iconv_open (tocode, fromcode);
691         if (cd->iconv_cd == (iconv_t) (-1))
692         {
693             xfree (cd);
694             return 0;
695         }
696     }
697 #else
698     if (!cd->read_handle || !cd->write_handle)
699     {
700         xfree (cd);
701         return 0;
702     }
703 #endif
704     cd->init_flag = 1;
705     return cd;
706 }
707
708 size_t yaz_iconv(yaz_iconv_t cd, char **inbuf, size_t *inbytesleft,
709                  char **outbuf, size_t *outbytesleft)
710 {
711     char *inbuf0;
712     size_t r = 0;
713
714 #if HAVE_ICONV_H
715     if (cd->iconv_cd)
716     {
717         size_t r =
718             iconv(cd->iconv_cd, inbuf, inbytesleft, outbuf, outbytesleft);
719         if (r == (size_t)(-1))
720         {
721             switch (yaz_errno())
722             {
723             case E2BIG:
724                 cd->my_errno = YAZ_ICONV_E2BIG;
725                 break;
726             case EINVAL:
727                 cd->my_errno = YAZ_ICONV_EINVAL;
728                 break;
729             case EILSEQ:
730                 cd->my_errno = YAZ_ICONV_EILSEQ;
731                 break;
732             default:
733                 cd->my_errno = YAZ_ICONV_UNKNOWN;
734             }
735         }
736         return r;
737     }
738 #endif
739     if (inbuf == 0 || *inbuf == 0)
740     {
741         cd->init_flag = 1;
742         cd->my_errno = YAZ_ICONV_UNKNOWN;
743         return 0;
744     }
745     inbuf0 = *inbuf;
746
747     if (cd->init_flag)
748     {
749         if (cd->init_handle)
750         {
751             size_t no_read;
752             size_t r = (cd->init_handle)(cd, (unsigned char *) *inbuf,
753                                          *inbytesleft, &no_read);
754             if (r)
755             {
756                 if (cd->my_errno == YAZ_ICONV_EINVAL)
757                     return r;
758                 cd->init_flag = 0;
759                 return r;
760             }
761             *inbytesleft -= no_read;
762             *inbuf += no_read;
763         }
764         cd->init_flag = 0;
765         cd->unget_x = 0;
766         cd->no_read_x = 0;
767     }
768     while (1)
769     {
770         unsigned long x;
771         size_t no_read;
772
773         if (*inbytesleft == 0)
774         {
775             r = *inbuf - inbuf0;
776             break;
777         }
778         if (!cd->unget_x)
779         {
780             x = (cd->read_handle)(cd, (unsigned char *) *inbuf, *inbytesleft,
781                                   &no_read);
782             if (no_read == 0)
783             {
784                 r = (size_t)(-1);
785                 break;
786             }
787         }
788         else
789         {
790             x = cd->unget_x;
791             no_read = cd->no_read_x;
792         }
793         if (x)
794         {
795             r = (cd->write_handle)(cd, x, outbuf, outbytesleft,
796                                    (*inbytesleft - no_read) == 0 ? 1 : 0);
797             if (r)
798             {
799                 /* unable to write it. save it because read_handle cannot
800                    rewind .. */
801                 if (cd->my_errno == YAZ_ICONV_E2BIG)
802                 {
803                     cd->unget_x = x;
804                     cd->no_read_x = no_read;
805                     break;
806                 }
807             }
808             cd->unget_x = 0;
809         }
810         *inbytesleft -= no_read;
811         (*inbuf) += no_read;
812     }
813     return r;
814 }
815
816 int yaz_iconv_error (yaz_iconv_t cd)
817 {
818     return cd->my_errno;
819 }
820
821 int yaz_iconv_close (yaz_iconv_t cd)
822 {
823 #if HAVE_ICONV_H
824     if (cd->iconv_cd)
825         iconv_close (cd->iconv_cd);
826 #endif
827     xfree (cd);
828     return 0;
829 }
830
831     
832 /*
833  * Local variables:
834  * c-basic-offset: 4
835  * indent-tabs-mode: nil
836  * End:
837  * vim: shiftwidth=4 tabstop=8 expandtab
838  */
839