]> git.lyx.org Git - lyx.git/blob - src/support/Messages.cpp
Merge branch 'master' into biblatex2
[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 <boost/cstdint.hpp>
87
88 #include <cerrno>
89 #include <fstream>
90 #include <utility>
91
92 #ifdef HAVE_SYS_STAT_H
93 # include <sys/stat.h>
94 #endif
95
96 using namespace std;
97 using boost::uint32_t;
98
99 namespace lyx {
100
101 void cleanTranslation(docstring & trans)
102 {
103         /*
104           Some english words have different translations, depending on
105           context. In these cases the original string is augmented by
106           context information (e.g. "To:[[as in 'From page x to page
107           y']]" and "To:[[as in 'From format x to format y']]". Also,
108           when placeholders are used, the context can indicate what will
109           be substituted for the placeholder (e.g. "%1$s[[date]], %1$s
110           [[time]]). This means that we need to filter out everything
111           in double square brackets at the end of the string, otherwise
112           the user sees bogus messages. If we are unable to honour the
113           request we just return what we got in.
114         */
115         static docstring const ctx_start = from_ascii("[[");
116         static docstring const ctx_end = from_ascii("]]");
117         while (true) {
118                 size_t const pos1 = trans.find(ctx_start);
119                 if (pos1 != docstring::npos) {
120                         size_t const pos2 = trans.find(ctx_end, pos1);
121                         if (pos2 != docstring::npos) {
122                                 trans.erase(pos1, pos2 - pos1 + 2);
123                                 continue;
124                         }
125                 }
126                 break;
127         }
128 }
129
130 } // lyx
131
132
133 #ifdef ENABLE_NLS
134
135 using namespace lyx::support;
136
137 namespace lyx {
138
139 std::string Messages::gui_lang_;
140
141
142 Messages::Messages(string const & l)
143         : lang_(l)
144 {
145         // strip off any encoding suffix, i.e., assume 8-bit po files
146         size_t i = lang_.find(".");
147         lang_ = lang_.substr(0, i);
148         LYXERR(Debug::LOCALE, "language(" << lang_ << ")");
149
150         readMoFile();
151 }
152
153
154 namespace {
155
156 // Find the code we have for a given language code. Return empty if not found.
157 string realCode(string code)
158 {
159         // this loops at most twice
160         while (true) {
161                 if (package().messages_file(code).isReadableFile())
162                         return code;
163                 if (contains(code, '_'))
164                         code = token(code, '_', 0);
165                 else
166                         break;
167         }
168         return string();
169 }
170 }
171
172
173 bool Messages::available(string const & c)
174 {
175         return !realCode(c).empty();
176 }
177
178
179 string Messages::language() const
180 {
181         return realCode(lang_);
182 }
183
184 namespace {
185
186 void swapInt(uint32_t & number)
187 {
188         unsigned char * num_ar = reinterpret_cast<unsigned char *>(&number);
189         swap(num_ar[0], num_ar[3]);
190         swap(num_ar[1], num_ar[2]);
191 }
192
193
194 struct MoHeader
195 {
196         // magic number = 0x950412de
197         uint32_t magic;
198         // file format revision = 0
199         uint32_t rev;
200         // number of strings
201         uint32_t N;
202         // offset of table with original strings
203         uint32_t O;
204         // offset of table with translation strings
205         uint32_t T;
206         // there is a hash table afterwards, but we ignore it
207
208         // Change the endianness of header data
209         void swapEnd();
210 };
211
212
213 void MoHeader::swapEnd()
214 {
215         swapInt(magic);
216         swapInt(rev);
217         swapInt(N);
218         swapInt(O);
219         swapInt(T);
220 }
221
222 struct StringTable
223 {
224         // string length
225         uint32_t length;
226         // string offset
227         uint32_t offset;
228
229         // Change the endianness of string stable data
230         void swapEnd();
231 };
232
233
234 void StringTable::swapEnd()
235 {
236         swapInt(length);
237         swapInt(offset);
238 }
239
240
241 } // namespace anon
242
243 bool Messages::readMoFile()
244 {
245         // FIXME:remove
246         if (lang_.empty()) {
247                 LYXERR0("No language given, nothing to load.");
248                 return false;
249         }
250
251         string const code = realCode(lang_);
252         if (code.empty()) {
253                 LYXERR(Debug::LOCALE, "Cannot find translation for language " << lang_);
254                 return false;
255         }
256
257         string const filen = package().messages_file(code).toSafeFilesystemEncoding();
258
259         // get file size
260         struct stat buf;
261         if (stat(filen.c_str(), &buf)) {
262                 LYXERR0("Cannot get information for file " << filen);
263                 return false;
264         }
265
266         vector<char> moData(buf.st_size);
267
268         ifstream is(filen.c_str(), ios::in | ios::binary);
269         if (!is.read(&moData[0], buf.st_size)) {
270                 LYXERR0("Cannot read file " << filen);
271                 return false;
272         }
273
274         MoHeader * header = reinterpret_cast<MoHeader *>(&moData[0]);
275
276         bool doSwap = false;
277         if (header->magic == 0xde120495) {
278                 header->swapEnd();
279                 doSwap = true;
280         }
281
282         if (header->magic != 0x950412de) {
283                 LYXERR0("Wrong magic number for file " << filen
284                         << ".\nExpected 0x950412de, got 0x" << std::hex << header->magic << std::dec);
285                 return false;
286         }
287
288         StringTable * orig = reinterpret_cast<StringTable *>(&moData[0] + header->O);
289         StringTable * trans = reinterpret_cast<StringTable *>(&moData[0] + header->T);
290         // First the header
291         if (doSwap) {
292                 // Handle endiannness change
293                 orig[0].swapEnd();
294                 trans[0].swapEnd();
295         }
296         string const info = string(&moData[0] + trans[0].offset, trans[0].length);
297         size_t pos = info.find("charset=");
298         if (pos != string::npos) {
299                 pos += 8;
300                 string charset;
301                 size_t pos2 = info.find("\n", pos);
302                 if (pos2 == string::npos)
303                         charset = info.substr(pos);
304                 else
305                         charset = info.substr(pos, pos2 - pos);
306                 charset = ascii_lowercase(trim(charset));
307                 if (charset != "utf-8") {
308                         LYXERR0("Wrong encoding " << charset << " for file " << filen);
309                         return false;
310                 }
311         } else {
312                 LYXERR0("Cannot find encoding encoding for file " << filen);
313                 return false;
314         }
315
316         for (size_t i = 1; i < header->N; ++i) {
317                 if (doSwap) {
318                         // Handle endiannness change
319                         orig[i].swapEnd();
320                         trans[i].swapEnd();
321                 }
322                 // Note that in theory the strings may contain NUL characters.
323                 // This may be the case with plural forms
324                 string const ostr(&moData[0] + orig[i].offset, orig[i].length);
325                 docstring tstr = from_utf8(string(&moData[0] + trans[i].offset,
326                                                   trans[i].length));
327                 cleanTranslation(tstr);
328                 trans_map_[ostr] = tstr;
329                 //lyxerr << ostr << " ==> " << tstr << endl;
330         }
331
332         return true;
333 }
334
335 docstring const Messages::get(string const & m) const
336 {
337         if (m.empty())
338                 return docstring();
339
340         TranslationMap::const_iterator it = trans_map_.find(m);
341         if (it != trans_map_.end())
342                 return it->second;
343         else {
344                 docstring res = from_utf8(m);
345                 cleanTranslation(res);
346                 return res;
347         }
348 }
349
350 } // namespace lyx
351
352 #else // ENABLE_NLS
353 // This is the dummy variant.
354
355 namespace lyx {
356
357 std::string Messages::gui_lang_;
358
359 Messages::Messages(string const & /* l */) {}
360
361 docstring const Messages::get(string const & m) const
362 {
363         docstring trans = from_ascii(m);
364         cleanTranslation(trans);
365         return trans;
366 }
367
368 std::string Messages::language() const
369 {
370         return string();
371 }
372
373 bool Messages::available(string const & /* c */)
374 {
375         return false;
376 }
377
378 } // namespace lyx
379
380 #endif