]> git.lyx.org Git - lyx.git/blob - src/support/docstream.cpp
82ec8dc54d270e25a9b85381db0599a647426abb
[lyx.git] / src / support / docstream.cpp
1 /**
2  * \file docstream.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Georg Baum
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "support/docstream.h"
14 #include "support/unicode.h"
15
16 #include <cerrno>
17 #include <cstdio>
18 #include <iconv.h>
19 #include <locale>
20
21 using namespace std;
22
23 using lyx::ucs4_codeset;
24
25 namespace {
26
27 // We use C IO throughout this file, because the facets might be used with
28 // lyxerr in the future.
29
30
31 /// codecvt facet for conversion of UCS4 (internal representation) to UTF8
32 /// (external representation) or vice versa
33 class iconv_codecvt_facet : public codecvt<lyx::char_type, char, mbstate_t>
34 {
35         typedef codecvt<lyx::char_type, char, mbstate_t> base;
36 public:
37         /// Constructor. You have to specify with \p inout whether you want
38         /// to use this facet only for input, only for output or for both.
39         explicit iconv_codecvt_facet(string const & encoding = "UTF-8",
40                         ios_base::openmode inout = ios_base::in | ios_base::out,
41                         size_t refs = 0)
42                 : base(refs), encoding_(encoding)
43         {
44                 if (inout & ios_base::in) {
45                         in_cd_ = iconv_open(ucs4_codeset, encoding.c_str());
46                         if (in_cd_ == (iconv_t)(-1)) {
47                                 fprintf(stderr, "Error %d returned from iconv_open(in_cd_): %s\n",
48                                         errno, strerror(errno));
49                                 fflush(stderr);
50                                 throw lyx::iconv_codecvt_facet_exception();
51                         }
52                 } else
53                         in_cd_ = (iconv_t)(-1);
54                 if (inout & ios_base::out) {
55                         out_cd_ = iconv_open(encoding.c_str(), ucs4_codeset);
56                         if (out_cd_ == (iconv_t)(-1)) {
57                                 fprintf(stderr, "Error %d returned from iconv_open(out_cd_): %s\n",
58                                         errno, strerror(errno));
59                                 fflush(stderr);
60                                 throw lyx::iconv_codecvt_facet_exception();
61                         }
62                 } else
63                         out_cd_ = (iconv_t)(-1);
64         }
65 protected:
66         virtual ~iconv_codecvt_facet()
67         {
68                 if (in_cd_ != (iconv_t)(-1))
69                         if (iconv_close(in_cd_) == -1) {
70                                 fprintf(stderr, "Error %d returned from iconv_close(in_cd_): %s\n",
71                                         errno, strerror(errno));
72                                 fflush(stderr);
73                         }
74                 if (out_cd_ != (iconv_t)(-1))
75                         if (iconv_close(out_cd_) == -1) {
76                                 fprintf(stderr, "Error %d returned from iconv_close(out_cd_): %s\n",
77                                         errno, strerror(errno));
78                                 fflush(stderr);
79                         }
80         }
81         virtual result do_out(state_type &, intern_type const * from,
82                         intern_type const * from_end, intern_type const *& from_next,
83                         extern_type * to, extern_type * to_end,
84                         extern_type *& to_next) const
85         {
86                 size_t inbytesleft = (from_end - from) * sizeof(intern_type);
87                 size_t outbytesleft = (to_end - to) * sizeof(extern_type);
88                 from_next = from;
89                 to_next = to;
90                 result const retval = do_iconv(out_cd_,
91                                 reinterpret_cast<char const **>(&from_next),
92                                 &inbytesleft, &to_next, &outbytesleft);
93                 if (retval == base::error) {
94                         fprintf(stderr,
95                                 "Error %d returned from iconv when converting from %s to %s: %s\n",
96                                 errno, ucs4_codeset, encoding_.c_str(),
97                                 strerror(errno));
98                         fputs("Converted input:", stderr);
99                         for (intern_type const * i = from; i < from_next; ++i) {
100                                 unsigned int const c = *i;
101                                 fprintf(stderr, " 0x%04x", c);
102                         }
103                         unsigned int const c = *from_next;
104                         fprintf(stderr, "\nStopped at: 0x%04x\n", c);
105                         fputs("Unconverted input:", stderr);
106                         for (intern_type const * i = from_next + 1; i < from_end; ++i) {
107                                 unsigned int const c = *i;
108                                 fprintf(stderr, " 0x%04x", c);
109                         }
110                         fputs("\nConverted output:", stderr);
111                         for (extern_type const * i = to; i < to_next; ++i) {
112                                 // extern_type may be signed, avoid output of
113                                 // something like 0xffffffc2
114                                 unsigned int const c =
115                                         *reinterpret_cast<unsigned char const *>(i);
116                                 fprintf(stderr, " 0x%02x", c);
117                         }
118                         fputc('\n', stderr);
119                         fflush(stderr);
120                 }
121                 return retval;
122         }
123         virtual result do_unshift(state_type &, extern_type * to,
124                         extern_type *, extern_type *& to_next) const
125         {
126                 // utf8 does not use shifting
127                 to_next = to;
128                 return base::noconv;
129         }
130         virtual result do_in(state_type &,
131                         extern_type const * from, extern_type const * from_end,
132                         extern_type const *& from_next,
133                         intern_type * to, intern_type * to_end,
134                         intern_type *& to_next) const
135         {
136                 size_t inbytesleft = (from_end - from) * sizeof(extern_type);
137                 size_t outbytesleft = (to_end - to) * sizeof(intern_type);
138                 from_next = from;
139                 to_next = to;
140                 result const retval = do_iconv(in_cd_, &from_next, &inbytesleft,
141                                 reinterpret_cast<char **>(&to_next),
142                                 &outbytesleft);
143                 if (retval == base::error) {
144                         fprintf(stderr,
145                                 "Error %d returned from iconv when converting from %s to %s: %s\n",
146                                 errno, encoding_.c_str(), ucs4_codeset,
147                                 strerror(errno));
148                         fputs("Converted input:", stderr);
149                         for (extern_type const * i = from; i < from_next; ++i) {
150                                 // extern_type may be signed, avoid output of
151                                 // something like 0xffffffc2
152                                 unsigned int const c =
153                                         *reinterpret_cast<unsigned char const *>(i);
154                                 fprintf(stderr, " 0x%02x", c);
155                         }
156                         unsigned int const c =
157                                 *reinterpret_cast<unsigned char const *>(from_next);
158                         fprintf(stderr, "\nStopped at: 0x%02x\n", c);
159                         fputs("Unconverted input:", stderr);
160                         for (extern_type const * i = from_next + 1; i < from_end; ++i) {
161                                 unsigned int const c =
162                                         *reinterpret_cast<unsigned char const *>(i);
163                                 fprintf(stderr, " 0x%02x", c);
164                         }
165                         fputs("\nConverted output:", stderr);
166                         for (intern_type const * i = to; i < to_next; ++i) {
167                                 unsigned int const c = *i;
168                                 fprintf(stderr, " 0x%02x", c);
169                         }
170                         fputc('\n', stderr);
171                         fflush(stderr);
172                 }
173                 return retval;
174         }
175         virtual int do_encoding() const throw()
176         {
177                 return 0;
178         }
179         virtual bool do_always_noconv() const throw()
180         {
181                 return false;
182         }
183         virtual int do_length(state_type & /*state*/, extern_type const * from,
184                         extern_type const * end, size_t max) const
185         {
186                 // The docs are a bit unclear about this method.
187                 // It seems that we should calculate the actual length of the
188                 // converted sequence, but that would not make sense, since
189                 // once could just do the conversion directly.
190                 // Therefore we just return the number of unconverted
191                 // characters, since that is the best guess we can do.
192 #if 0
193                 intern_type * to = new intern_type[max];
194                 intern_type * to_end = to + max;
195                 intern_type * to_next = to;
196                 extern_type const * from_next = from;
197                 do_in(state, from, end, from_next, to, to_end, to_next);
198                 delete[] to;
199                 return to_next - to;
200 #else
201                 size_t const length = end - from;
202                 return min(length, max);
203 #endif
204         }
205         virtual int do_max_length() const throw()
206         {
207                 // FIXME: this information should be transferred to lib/encodings
208                 // UTF8 uses at most 4 bytes to represent one UCS4 code point
209                 // (see RFC 3629). RFC 2279 specifies 6 bytes, but that
210                 // information is outdated, and RFC 2279 has been superseded by
211                 // RFC 3629.
212                 // The CJK encodings use (different) multibyte representation as well.
213                 // All other encodings encode one UCS4 code point in one byte
214                 // (and can therefore only encode a subset of UCS4)
215                 // Note that BIG5 and SJIS do not work with LaTeX (see lib/encodings). 
216                 // Furthermore, all encodings that use shifting (like SJIS) do not work with 
217                 // iconv_codecvt_facet.
218                 if (encoding_ == "UTF-8" ||
219                     encoding_ == "GB" ||
220                     encoding_ == "EUC-TW")
221                         return 4;
222                 else if (encoding_ == "EUC-JP")
223                         return 3;
224                 else if (encoding_ == "BIG5" ||
225                          encoding_ == "EUC-KR" ||
226                          encoding_ == "EUC-CN" ||
227                          encoding_ == "SJIS" ||
228                          encoding_ == "GBK" ||
229                          encoding_ == "JIS" )
230                         return 2;
231                 else
232                         return 1;
233         }
234 private:
235         /// Do the actual conversion. The interface is equivalent to that of
236         /// iconv() (but const correct).
237         inline base::result do_iconv(iconv_t cd, char const ** from,
238                         size_t * inbytesleft, char ** to, size_t * outbytesleft) const
239         {
240                 char const * const to_start = *to;
241                 size_t converted = iconv(cd, const_cast<char ICONV_CONST **>(from),
242                                 inbytesleft, to, outbytesleft);
243                 if (converted == (size_t)(-1)) {
244                         switch(errno) {
245                         case EINVAL:
246                         case E2BIG:
247                                 return base::partial;
248                         case EILSEQ:
249                         default:
250                                 return base::error;
251                         }
252                 }
253                 if (*to == to_start)
254                         return base::noconv;
255                 return base::ok;
256         }
257         iconv_t in_cd_;
258         iconv_t out_cd_;
259         /// The narrow encoding
260         string encoding_;
261 };
262
263 } // namespace anon
264
265
266 namespace lyx {
267
268 template<class Ios>
269 void setEncoding(Ios & ios, string const & encoding, ios_base::openmode mode)
270 {
271         // We must imbue the stream before openening the file
272         locale global;
273         locale locale(global, new iconv_codecvt_facet(encoding, mode));
274         ios.imbue(locale);
275 }
276
277
278 const char * iconv_codecvt_facet_exception::what() const throw()
279 {
280         return "iconv problem in iconv_codecvt_facet initialization";
281 }
282
283
284 idocfstream::idocfstream(string const & encoding) : base()
285 {
286         setEncoding(*this, encoding, in);
287 }
288
289
290 idocfstream::idocfstream(const char* s, ios_base::openmode mode,
291                          string const & encoding)
292         : base()
293 {
294         setEncoding(*this, encoding, in);
295         open(s, mode);
296 }
297
298
299 odocfstream::odocfstream(): base()
300 {
301         setEncoding(*this, "UTF-8", out);
302 }
303
304
305 odocfstream::odocfstream(const char* s, ios_base::openmode mode,
306                          string const & encoding)
307         : base()
308 {
309         setEncoding(*this, encoding, out);
310         open(s, mode);
311 }
312
313
314 void odocfstream::reset(string const & encoding)
315 {
316         setEncoding(*this, encoding, out);
317 }
318
319
320
321 SetEnc setEncoding(string const & encoding)
322 {
323         return SetEnc(encoding);
324 }
325
326
327 odocstream & operator<<(odocstream & os, SetEnc e)
328 {
329         if (has_facet<iconv_codecvt_facet>(os.rdbuf()->getloc())) {
330                 // This stream must be a file stream, since we never imbue
331                 // any other stream with a locale having a iconv_codecvt_facet.
332                 // Flush the stream so that all pending output is written
333                 // with the old encoding.
334                 os.flush();
335                 locale locale(os.rdbuf()->getloc(),
336                         new iconv_codecvt_facet(e.encoding, ios_base::out));
337                 // FIXME Does changing the codecvt facet of an open file
338                 // stream always work? It does with gcc 4.1, but I have read
339                 // somewhere that it does not with MSVC.
340                 // What does the standard say?
341                 os.imbue(locale);
342         }
343         return os;
344 }
345
346
347 #if ! defined(USE_WCHAR_T)
348 odocstream & operator<<(odocstream & os, char c)
349 {
350         os.put(c);
351         return os;
352 }
353 #endif
354
355 }
356
357 #if ! defined(USE_WCHAR_T) && defined(__GNUC__)
358 // We get undefined references to these virtual methods. This looks like
359 // a bug in gcc. The implementation here does not do anything useful, since
360 // it is overriden in iconv_codecvt_facet.
361 namespace std {
362
363 template<> codecvt<lyx::char_type, char, mbstate_t>::result
364 codecvt<lyx::char_type, char, mbstate_t>::do_out(
365         mbstate_t &, const lyx::char_type *, const lyx::char_type *,
366         const lyx::char_type *&, char *, char *, char *&) const
367 {
368         return error;
369 }
370
371
372 template<> codecvt<lyx::char_type, char, mbstate_t>::result
373 codecvt<lyx::char_type, char, mbstate_t>::do_unshift(
374         mbstate_t &, char *, char *, char *&) const
375 {
376         return error;
377 }
378
379
380 template<> codecvt<lyx::char_type, char, mbstate_t>::result
381 codecvt<lyx::char_type, char, mbstate_t>::do_in(
382         mbstate_t &, const char *, const char *, const char *&,
383         lyx::char_type *, lyx::char_type *, lyx::char_type *&) const
384 {
385         return error;
386 }
387
388
389 template<>
390 int codecvt<lyx::char_type, char, mbstate_t>::do_encoding() const throw()
391 {
392         return 0;
393 }
394
395
396 template<>
397 bool codecvt<lyx::char_type, char, mbstate_t>::do_always_noconv() const throw()
398 {
399         return true;
400 }
401
402 #if __GNUC__ == 3 && __GNUC_MINOR__ < 4
403
404 template<>
405 int codecvt<lyx::char_type, char, mbstate_t>::do_length(
406         mbstate_t const &, const char *, const char *, size_t) const
407 {
408         return 1;
409 }
410
411 #else
412
413 template<>
414 int codecvt<lyx::char_type, char, mbstate_t>::do_length(
415         mbstate_t &, const char *, const char *, size_t) const
416 {
417         return 1;
418 }
419
420 #endif
421
422 template<>
423 int codecvt<lyx::char_type, char, mbstate_t>::do_max_length() const throw()
424 {
425         return 4;
426 }
427
428 } // namespace std
429 #endif