]> git.lyx.org Git - lyx.git/blob - src/support/docstream.cpp
Introduce FileName::changePermission() and fix ConverterCache.
[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 }
302
303
304 odocfstream::odocfstream(string const & encoding) : base()
305 {
306         setEncoding(*this, encoding, out);
307 }
308
309
310 odocfstream::odocfstream(const char* s, ios_base::openmode mode,
311                          string const & encoding)
312         : base()
313 {
314         setEncoding(*this, encoding, out);
315         open(s, mode);
316 }
317
318
319 void odocfstream::reset(string const & encoding)
320 {
321         setEncoding(*this, encoding, out);
322 }
323
324
325
326 SetEnc setEncoding(string const & encoding)
327 {
328         return SetEnc(encoding);
329 }
330
331
332 odocstream & operator<<(odocstream & os, SetEnc e)
333 {
334         if (has_facet<iconv_codecvt_facet>(os.rdbuf()->getloc())) {
335                 // This stream must be a file stream, since we never imbue
336                 // any other stream with a locale having a iconv_codecvt_facet.
337                 // Flush the stream so that all pending output is written
338                 // with the old encoding.
339                 os.flush();
340                 locale locale(os.rdbuf()->getloc(),
341                         new iconv_codecvt_facet(e.encoding, ios_base::out));
342                 // FIXME Does changing the codecvt facet of an open file
343                 // stream always work? It does with gcc 4.1, but I have read
344                 // somewhere that it does not with MSVC.
345                 // What does the standard say?
346                 os.imbue(locale);
347         }
348         return os;
349 }
350
351
352 #if ! defined(USE_WCHAR_T)
353 odocstream & operator<<(odocstream & os, char c)
354 {
355         os.put(c);
356         return os;
357 }
358 #endif
359
360 }
361
362 #if ! defined(USE_WCHAR_T) && defined(__GNUC__)
363 // We get undefined references to these virtual methods. This looks like
364 // a bug in gcc. The implementation here does not do anything useful, since
365 // it is overriden in iconv_codecvt_facet.
366 namespace std {
367
368 template<> codecvt<lyx::char_type, char, mbstate_t>::result
369 codecvt<lyx::char_type, char, mbstate_t>::do_out(
370         mbstate_t &, const lyx::char_type *, const lyx::char_type *,
371         const lyx::char_type *&, char *, char *, char *&) const
372 {
373         return error;
374 }
375
376
377 template<> codecvt<lyx::char_type, char, mbstate_t>::result
378 codecvt<lyx::char_type, char, mbstate_t>::do_unshift(
379         mbstate_t &, char *, char *, char *&) const
380 {
381         return error;
382 }
383
384
385 template<> codecvt<lyx::char_type, char, mbstate_t>::result
386 codecvt<lyx::char_type, char, mbstate_t>::do_in(
387         mbstate_t &, const char *, const char *, const char *&,
388         lyx::char_type *, lyx::char_type *, lyx::char_type *&) const
389 {
390         return error;
391 }
392
393
394 template<>
395 int codecvt<lyx::char_type, char, mbstate_t>::do_encoding() const throw()
396 {
397         return 0;
398 }
399
400
401 template<>
402 bool codecvt<lyx::char_type, char, mbstate_t>::do_always_noconv() const throw()
403 {
404         return true;
405 }
406
407 #if __GNUC__ == 3 && __GNUC_MINOR__ < 4
408
409 template<>
410 int codecvt<lyx::char_type, char, mbstate_t>::do_length(
411         mbstate_t const &, const char *, const char *, size_t) const
412 {
413         return 1;
414 }
415
416 #else
417
418 template<>
419 int codecvt<lyx::char_type, char, mbstate_t>::do_length(
420         mbstate_t &, const char *, const char *, size_t) const
421 {
422         return 1;
423 }
424
425 #endif
426
427 template<>
428 int codecvt<lyx::char_type, char, mbstate_t>::do_max_length() const throw()
429 {
430         return 4;
431 }
432
433 } // namespace std
434 #endif