]> git.lyx.org Git - lyx.git/blob - src/support/Messages.cpp
Fix some warnings
[lyx.git] / src / support / Messages.cpp
1 /* \file Messages.cpp
2  * This file is part of LyX, the document processor.
3  * Licence details can be found in the file COPYING.
4  *
5  * \author Lars Gullik Bjønnes
6  * \author Jean-Marc Lasgouttes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 /*
12   This contains a limited parser for gettext's mo files. Several features are
13   not implemented currently:
14    * encoding is supposed to be UTF-8 (the charset parameter is enforced)
15    * context is not handled (implemented differently in LyX)
16    * plural forms are not implemented (not used for now in LyX).
17
18   The data is loaded in a std::map object for simplicity.
19  */
20
21 /*
22   Format of a MO file. Source: http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
23
24              byte
25                   +------------------------------------------+
26                0  | magic number = 0x950412de                |
27                   |                                          |
28                4  | file format revision = 0                 |
29                   |                                          |
30                8  | number of strings                        |  == N
31                   |                                          |
32               12  | offset of table with original strings    |  == O
33                   |                                          |
34               16  | offset of table with translation strings |  == T
35                   |                                          |
36               20  | size of hashing table                    |  == S
37                   |                                          |
38               24  | offset of hashing table                  |  == H
39                   |                                          |
40                   .                                          .
41                   .    (possibly more entries later)         .
42                   .                                          .
43                   |                                          |
44                O  | length & offset 0th string  ----------------.
45            O + 8  | length & offset 1st string  ------------------.
46                    ...                                    ...   | |
47      O + ((N-1)*8)| length & offset (N-1)th string           |  | |
48                   |                                          |  | |
49                T  | length & offset 0th translation  ---------------.
50            T + 8  | length & offset 1st translation  -----------------.
51                    ...                                    ...   | | | |
52      T + ((N-1)*8)| length & offset (N-1)th translation      |  | | | |
53                   |                                          |  | | | |
54                H  | start hash table                         |  | | | |
55                    ...                                    ...   | | | |
56        H + S * 4  | end hash table                           |  | | | |
57                   |                                          |  | | | |
58                   | NUL terminated 0th string  <----------------' | | |
59                   |                                          |    | | |
60                   | NUL terminated 1st string  <------------------' | |
61                   |                                          |      | |
62                    ...                                    ...       | |
63                   |                                          |      | |
64                   | NUL terminated 0th translation  <---------------' |
65                   |                                          |        |
66                   | NUL terminated 1st translation  <-----------------'
67                   |                                          |
68                    ...                                    ...
69                   |                                          |
70                   +------------------------------------------+
71
72  */
73
74 #include <config.h>
75
76 #include "support/Messages.h"
77
78 #include "support/debug.h"
79 #include "support/docstring.h"
80 #include "support/lstrings.h"
81 #include "support/Package.h"
82 #include "support/unicode.h"
83
84 #include "support/lassert.h"
85
86 #include <cerrno>
87 #include <cstdint>
88 #include <fstream>
89 #include <utility>
90
91 #ifdef HAVE_SYS_STAT_H
92 # include <sys/stat.h>
93 #endif
94
95 using namespace std;
96
97 namespace lyx {
98
99 void cleanTranslation(docstring & trans)
100 {
101         /*
102           Some english words have different translations, depending on
103           context. In these cases the original string is augmented by
104           context information (e.g. "To:[[as in 'From page x to page
105           y']]" and "To:[[as in 'From format x to format y']]". Also,
106           when placeholders are used, the context can indicate what will
107           be substituted for the placeholder (e.g. "%1$s[[date]], %1$s
108           [[time]]). This means that we need to filter out everything
109           in double square brackets at the end of the string, otherwise
110           the user sees bogus messages. If we are unable to honour the
111           request we just return what we got in.
112         */
113         static docstring const ctx_start = from_ascii("[[");
114         static docstring const ctx_end = from_ascii("]]");
115         while (true) {
116                 size_t const pos1 = trans.find(ctx_start);
117                 if (pos1 != docstring::npos) {
118                         size_t const pos2 = trans.find(ctx_end, pos1);
119                         if (pos2 != docstring::npos) {
120                                 trans.erase(pos1, pos2 - pos1 + 2);
121                                 continue;
122                         }
123                 }
124                 break;
125         }
126 }
127
128 } // namespace lyx
129
130
131 #ifdef ENABLE_NLS
132
133 using namespace lyx::support;
134
135 namespace lyx {
136
137 std::string Messages::gui_lang_;
138
139
140 Messages::Messages(string const & l)
141         : lang_(l)
142 {
143         // strip off any encoding suffix, i.e., assume 8-bit po files
144         size_t i = lang_.find(".");
145         lang_ = lang_.substr(0, i);
146         LYXERR(Debug::LOCALE, "language(" << lang_ << ")");
147
148         readMoFile();
149 }
150
151
152 namespace {
153
154 // Find the code we have for a given language code. Return empty if not found.
155 string realCode(string code)
156 {
157         // this loops at most twice
158         while (true) {
159                 if (package().messages_file(code).isReadableFile())
160                         return code;
161                 if (contains(code, '_'))
162                         code = token(code, '_', 0);
163                 else
164                         break;
165         }
166         return string();
167 }
168 } // namespace
169
170
171 bool Messages::available(string const & c)
172 {
173         return !realCode(c).empty();
174 }
175
176
177 string Messages::language() const
178 {
179         return realCode(lang_);
180 }
181
182 namespace {
183
184 void swapInt(uint32_t & number)
185 {
186         unsigned char * num_ar = reinterpret_cast<unsigned char *>(&number);
187         swap(num_ar[0], num_ar[3]);
188         swap(num_ar[1], num_ar[2]);
189 }
190
191
192 struct MoHeader
193 {
194         // magic number = 0x950412de
195         uint32_t magic;
196         // file format revision = 0
197         uint32_t rev;
198         // number of strings
199         uint32_t N;
200         // offset of table with original strings
201         uint32_t O;
202         // offset of table with translation strings
203         uint32_t T;
204         // there is a hash table afterwards, but we ignore it
205
206         // Change the endianness of header data
207         void swapEnd();
208 };
209
210
211 void MoHeader::swapEnd()
212 {
213         swapInt(magic);
214         swapInt(rev);
215         swapInt(N);
216         swapInt(O);
217         swapInt(T);
218 }
219
220 struct StringTable
221 {
222         // string length
223         uint32_t length;
224         // string offset
225         uint32_t offset;
226
227         // Change the endianness of string stable data
228         void swapEnd();
229 };
230
231
232 void StringTable::swapEnd()
233 {
234         swapInt(length);
235         swapInt(offset);
236 }
237
238
239 } // namespace
240
241 bool Messages::readMoFile()
242 {
243         // FIXME:remove
244         if (lang_.empty()) {
245                 LYXERR0("No language given, nothing to load.");
246                 return false;
247         }
248
249         string const code = realCode(lang_);
250         if (code.empty()) {
251                 LYXERR(Debug::LOCALE, "Cannot find translation for language " << lang_);
252                 return false;
253         }
254
255         string const filen = package().messages_file(code).toSafeFilesystemEncoding();
256
257         // get file size
258         struct stat buf;
259         if (stat(filen.c_str(), &buf)) {
260                 LYXERR0("Cannot get information for file " << filen);
261                 return false;
262         }
263
264         vector<char> moData(buf.st_size);
265
266         ifstream is(filen.c_str(), ios::in | ios::binary);
267         if (!is.read(&moData[0], buf.st_size)) {
268                 LYXERR0("Cannot read file " << filen);
269                 return false;
270         }
271
272         MoHeader * header = reinterpret_cast<MoHeader *>(&moData[0]);
273
274         bool doSwap = false;
275         if (header->magic == 0xde120495) {
276                 header->swapEnd();
277                 doSwap = true;
278         }
279
280         if (header->magic != 0x950412de) {
281                 LYXERR0("Wrong magic number for file " << filen
282                         << ".\nExpected 0x950412de, got 0x" << std::hex << header->magic << std::dec);
283                 return false;
284         }
285
286         StringTable * orig = reinterpret_cast<StringTable *>(&moData[0] + header->O);
287         StringTable * trans = reinterpret_cast<StringTable *>(&moData[0] + header->T);
288         // First the header
289         if (doSwap) {
290                 // Handle endiannness change
291                 orig[0].swapEnd();
292                 trans[0].swapEnd();
293         }
294         string const info = string(&moData[0] + trans[0].offset, trans[0].length);
295         size_t pos = info.find("charset=");
296         if (pos != string::npos) {
297                 pos += 8;
298                 string charset;
299                 size_t pos2 = info.find("\n", pos);
300                 if (pos2 == string::npos)
301                         charset = info.substr(pos);
302                 else
303                         charset = info.substr(pos, pos2 - pos);
304                 charset = ascii_lowercase(trim(charset));
305                 if (charset != "utf-8") {
306                         LYXERR0("Wrong encoding " << charset << " for file " << filen);
307                         return false;
308                 }
309         } else {
310                 LYXERR0("Cannot find encoding encoding for file " << filen);
311                 return false;
312         }
313
314         for (size_t i = 1; i < header->N; ++i) {
315                 if (doSwap) {
316                         // Handle endiannness change
317                         orig[i].swapEnd();
318                         trans[i].swapEnd();
319                 }
320                 // Note that in theory the strings may contain NUL characters.
321                 // This may be the case with plural forms
322                 string const ostr(&moData[0] + orig[i].offset, orig[i].length);
323                 docstring tstr = from_utf8(string(&moData[0] + trans[i].offset,
324                                                   trans[i].length));
325                 cleanTranslation(tstr);
326                 trans_map_[ostr] = tstr;
327                 //lyxerr << ostr << " ==> " << tstr << endl;
328         }
329
330         return true;
331 }
332
333 docstring const Messages::get(string const & m) const
334 {
335         if (m.empty())
336                 return docstring();
337
338         TranslationMap::const_iterator it = trans_map_.find(m);
339         if (it != trans_map_.end())
340                 return it->second;
341         else {
342                 docstring res = from_utf8(m);
343                 cleanTranslation(res);
344                 return res;
345         }
346 }
347
348
349 docstring const Messages::getIfFound(string const & m) const
350 {
351         if (m.empty())
352                 return docstring();
353
354         TranslationMap::const_iterator it = trans_map_.find(m);
355         if (it != trans_map_.end())
356                 return it->second;
357         else
358                 return docstring();
359 }
360
361 } // namespace lyx
362
363 #else // ENABLE_NLS
364 // This is the dummy variant.
365
366 namespace lyx {
367
368 std::string Messages::gui_lang_;
369
370 Messages::Messages(string const & /* l */) {}
371
372 docstring const Messages::get(string const & m) const
373 {
374         docstring trans = from_ascii(m);
375         cleanTranslation(trans);
376         return trans;
377 }
378
379 std::string Messages::language() const
380 {
381         return string();
382 }
383
384 bool Messages::available(string const & /* c */)
385 {
386         return false;
387 }
388
389 docstring const Messages::getIfFound(string const & /* m */) const
390 {
391         return docstring();
392 }
393
394 } // namespace lyx
395
396 #endif