Imported Upstream version 0.18.1.1
[platform/upstream/gettext.git] / gnulib-local / lib / iconv-ostream.oo.c
1 /* Output stream that converts the output to another encoding.
2    Copyright (C) 2006-2007 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include "iconv-ostream.h"
22
23 #if HAVE_ICONV
24
25 #include <errno.h>
26 #include <iconv.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "c-strcase.h"
31 #include "error.h"
32 #include "xalloc.h"
33 #include "gettext.h"
34
35 #define _(str) gettext (str)
36
37 #endif /* HAVE_ICONV */
38
39 struct iconv_ostream : struct ostream
40 {
41 fields:
42 #if HAVE_ICONV
43   /* The destination stream.  */
44   ostream_t destination;
45   /* The from and to encodings.  */
46   char *from_encoding;
47   char *to_encoding;
48   /* The converter.  */
49   iconv_t cd;
50   /* Last few bytes that could not yet be converted.  */
51   #define BUFSIZE 64
52   char buf[BUFSIZE];
53   size_t buflen;
54 #endif /* HAVE_ICONV */
55 };
56
57 #if HAVE_ICONV
58
59 /* Implementation of ostream_t methods.  */
60
61 static void
62 iconv_ostream::write_mem (iconv_ostream_t stream, const void *data, size_t len)
63 {
64   if (len > 0)
65     {
66       #define BUFFERSIZE 256
67       char inbuffer[BUFFERSIZE];
68       size_t inbufcount;
69
70       inbufcount = stream->buflen;
71       if (inbufcount > 0)
72         memcpy (inbuffer, stream->buf, inbufcount);
73       for (;;)
74         {
75           /* At this point, inbuffer[0..inbufcount-1] is filled.  */
76           {
77             /* Combine the previous rest with a chunk of new input.  */
78             size_t n =
79               (len <= BUFFERSIZE - inbufcount ? len : BUFFERSIZE - inbufcount);
80
81             if (n > 0)
82               {
83                 memcpy (inbuffer + inbufcount, data, n);
84                 data = (char *) data + n;
85                 inbufcount += n;
86                 len -= n;
87               }
88           }
89           {
90             /* Attempt to convert the combined input.  */
91             char outbuffer[8*BUFFERSIZE];
92
93             const char *inptr = inbuffer;
94             size_t insize = inbufcount;
95             char *outptr = outbuffer;
96             size_t outsize = sizeof (outbuffer);
97
98             size_t res = iconv (stream->cd,
99                                 (ICONV_CONST char **) &inptr, &insize,
100                                 &outptr, &outsize);
101             #if !defined _LIBICONV_VERSION && !defined __GLIBC__
102             /* Irix iconv() inserts a NUL byte if it cannot convert.
103                NetBSD iconv() inserts a question mark if it cannot convert.
104                Only GNU libiconv and GNU libc are known to prefer to fail rather
105                than doing a lossy conversion.  */
106             if (res > 0)
107               {
108                 errno = EILSEQ;
109                 res = -1;
110               }
111             #endif
112             if (res == (size_t)(-1) && errno != EINVAL)
113               error (EXIT_FAILURE, 0, _("%s: cannot convert from %s to %s"),
114                      "iconv_ostream",
115                      stream->from_encoding, stream->to_encoding);
116             /* Output the converted part.  */
117             if (sizeof (outbuffer) - outsize > 0)
118               ostream_write_mem (stream->destination,
119                                  outbuffer, sizeof (outbuffer) - outsize);
120             /* Put back the unconverted part.  */
121             if (insize > BUFSIZE)
122               error (EXIT_FAILURE, 0, _("%s: shift sequence too long"),
123                      "iconv_ostream");
124             if (len == 0)
125               {
126                 if (insize > 0)
127                   memcpy (stream->buf, inptr, insize);
128                 stream->buflen = insize;
129                 break;
130               }
131             if (insize > 0)
132               memmove (inbuffer, inptr, insize);
133             inbufcount = insize;
134           }
135         }
136       #undef BUFFERSIZE
137     }
138 }
139
140 static void
141 iconv_ostream::flush (iconv_ostream_t stream)
142 {
143   /* There's nothing we can do here, since stream->buf[] contains only a few
144      bytes that don't correspond to a character.  */
145 }
146
147 static void
148 iconv_ostream::free (iconv_ostream_t stream)
149 {
150   /* Silently ignore the few bytes in stream->buf[] that don't correspond to a
151      character.  */
152
153   /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
154   #if defined _LIBICONV_VERSION \
155       || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
156   {
157     char outbuffer[2048];
158     char *outptr = outbuffer;
159     size_t outsize = sizeof (outbuffer);
160     size_t res = iconv (stream->cd, NULL, NULL, &outptr, &outsize);
161     if (res == (size_t)(-1))
162       error (EXIT_FAILURE, 0, _("%s: cannot convert from %s to %s"),
163              "iconv_ostream", stream->from_encoding, stream->to_encoding);
164     /* Output the converted part.  */
165     if (sizeof (outbuffer) - outsize > 0)
166       ostream_write_mem (stream->destination,
167                          outbuffer, sizeof (outbuffer) - outsize);
168   }
169   #endif
170
171   iconv_close (stream->cd);
172   free (stream->from_encoding);
173   free (stream->to_encoding);
174   free (stream);
175 }
176
177 /* Constructor.  */
178
179 iconv_ostream_t
180 iconv_ostream_create (const char *from_encoding, const char *to_encoding,
181                       ostream_t destination)
182 {
183   iconv_ostream_t stream = XMALLOC (struct iconv_ostream_representation);
184
185   stream->base.vtable = &iconv_ostream_vtable;
186   stream->destination = destination;
187   stream->from_encoding = xstrdup (from_encoding);
188   stream->to_encoding = xstrdup (to_encoding);
189
190   /* Avoid glibc-2.1 bug with EUC-KR.  */
191   #if  (__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) && !defined _LIBICONV_VERSION
192   if (c_strcasecmp (from_encoding, "EUC-KR") == 0
193       || c_strcasecmp (to_encoding, "EUC-KR") == 0)
194     stream->cd = (iconv_t)(-1):
195   else
196   #endif
197     stream->cd = iconv_open (to_encoding, from_encoding);
198   if (stream->cd == (iconv_t)(-1))
199     {
200       if (iconv_open ("UTF-8", from_encoding) == (iconv_t)(-1))
201         error (EXIT_FAILURE, 0, _("%s does not support conversion from %s"),
202                "iconv", from_encoding);
203       else if (iconv_open (to_encoding, "UTF-8") == (iconv_t)(-1))
204         error (EXIT_FAILURE, 0, _("%s does not support conversion to %s"),
205                "iconv", to_encoding);
206       else
207         error (EXIT_FAILURE, 0,
208                _("%s does not support conversion from %s to %s"),
209                "iconv", from_encoding, to_encoding);
210     }
211
212   stream->buflen = 0;
213
214   return stream;
215 }
216
217 #else
218
219 static void
220 iconv_ostream::write_mem (iconv_ostream_t stream, const void *data, size_t len)
221 {
222   abort ();
223 }
224
225 static void
226 iconv_ostream::flush (iconv_ostream_t stream)
227 {
228   abort ();
229 }
230
231 static void
232 iconv_ostream::free (iconv_ostream_t stream)
233 {
234   abort ();
235 }
236
237 #endif /* HAVE_ICONV */