1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qplatformdefs.h"
41
42#include "qtranslator.h"
43
44#ifndef QT_NO_TRANSLATION
45
46#include "qfileinfo.h"
47#include "qstring.h"
48#include "qstringlist.h"
49#include "qcoreapplication.h"
50#include "qcoreapplication_p.h"
51#include "qdatastream.h"
52#include "qendian.h"
53#include "qfile.h"
54#include "qmap.h"
55#include "qalgorithms.h"
56#include "qtranslator_p.h"
57#include "qlocale.h"
58#include "qendian.h"
59#include "qresource.h"
60
61#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY)
62# define QT_USE_MMAP
63# include "private/qcore_unix_p.h"
64// for mmap
65# include <sys/mman.h>
66#endif
67
68#include <stdlib.h>
69#include <new>
70
71#include "qobject_p.h"
72
73#include <vector>
74#include <memory>
75
76QT_BEGIN_NAMESPACE
77
78enum Tag { Tag_End = 1, Tag_SourceText16, Tag_Translation, Tag_Context16, Tag_Obsolete1,
79 Tag_SourceText, Tag_Context, Tag_Comment, Tag_Obsolete2 };
80/*
81$ mcookie
823cb86418caef9c95cd211cbf60a1bddd
83$
84*/
85
86// magic number for the file
87static const int MagicLength = 16;
88static const uchar magic[MagicLength] = {
89 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
90 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
91};
92
93static inline QString dotQmLiteral() { return QStringLiteral(".qm"); }
94
95static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
96{
97 // catch the case if \a found has a zero-terminating symbol and \a len includes it.
98 // (normalize it to be without the zero-terminating symbol)
99 if (foundLen > 0 && found[foundLen-1] == '\0')
100 --foundLen;
101 return ((targetLen == foundLen) && memcmp(found, target, foundLen) == 0);
102}
103
104static void elfHash_continue(const char *name, uint &h)
105{
106 const uchar *k;
107 uint g;
108
109 k = (const uchar *) name;
110 while (*k) {
111 h = (h << 4) + *k++;
112 if ((g = (h & 0xf0000000)) != 0)
113 h ^= g >> 24;
114 h &= ~g;
115 }
116}
117
118static void elfHash_finish(uint &h)
119{
120 if (!h)
121 h = 1;
122}
123
124static uint elfHash(const char *name)
125{
126 uint hash = 0;
127 elfHash_continue(name, hash);
128 elfHash_finish(hash);
129 return hash;
130}
131
132/*
133 \internal
134
135 Determines whether \a rules are valid "numerus rules". Test input with this
136 function before calling numerusHelper, below.
137 */
138static bool isValidNumerusRules(const uchar *rules, uint rulesSize)
139{
140 // Disabled computation of maximum numerus return value
141 // quint32 numerus = 0;
142
143 if (rulesSize == 0)
144 return true;
145
146 quint32 offset = 0;
147 do {
148 uchar opcode = rules[offset];
149 uchar op = opcode & Q_OP_MASK;
150
151 if (opcode & 0x80)
152 return false; // Bad op
153
154 if (++offset == rulesSize)
155 return false; // Missing operand
156
157 // right operand
158 ++offset;
159
160 switch (op)
161 {
162 case Q_EQ:
163 case Q_LT:
164 case Q_LEQ:
165 break;
166
167 case Q_BETWEEN:
168 if (offset != rulesSize) {
169 // third operand
170 ++offset;
171 break;
172 }
173 return false; // Missing operand
174
175 default:
176 return false; // Bad op (0)
177 }
178
179 // ++numerus;
180 if (offset == rulesSize)
181 return true;
182
183 } while (((rules[offset] == Q_AND)
184 || (rules[offset] == Q_OR)
185 || (rules[offset] == Q_NEWRULE))
186 && ++offset != rulesSize);
187
188 // Bad op
189 return false;
190}
191
192/*
193 \internal
194
195 This function does no validation of input and assumes it is well-behaved,
196 these assumptions can be checked with isValidNumerusRules, above.
197
198 Determines which translation to use based on the value of \a n. The return
199 value is an index identifying the translation to be used.
200
201 \a rules is a character array of size \a rulesSize containing bytecode that
202 operates on the value of \a n and ultimately determines the result.
203
204 This function has O(1) space and O(rulesSize) time complexity.
205 */
206static uint numerusHelper(int n, const uchar *rules, uint rulesSize)
207{
208 uint result = 0;
209 uint i = 0;
210
211 if (rulesSize == 0)
212 return 0;
213
214 for (;;) {
215 bool orExprTruthValue = false;
216
217 for (;;) {
218 bool andExprTruthValue = true;
219
220 for (;;) {
221 bool truthValue = true;
222 int opcode = rules[i++];
223
224 int leftOperand = n;
225 if (opcode & Q_MOD_10) {
226 leftOperand %= 10;
227 } else if (opcode & Q_MOD_100) {
228 leftOperand %= 100;
229 } else if (opcode & Q_LEAD_1000) {
230 while (leftOperand >= 1000)
231 leftOperand /= 1000;
232 }
233
234 int op = opcode & Q_OP_MASK;
235 int rightOperand = rules[i++];
236
237 switch (op) {
238 case Q_EQ:
239 truthValue = (leftOperand == rightOperand);
240 break;
241 case Q_LT:
242 truthValue = (leftOperand < rightOperand);
243 break;
244 case Q_LEQ:
245 truthValue = (leftOperand <= rightOperand);
246 break;
247 case Q_BETWEEN:
248 int bottom = rightOperand;
249 int top = rules[i++];
250 truthValue = (leftOperand >= bottom && leftOperand <= top);
251 }
252
253 if (opcode & Q_NOT)
254 truthValue = !truthValue;
255
256 andExprTruthValue = andExprTruthValue && truthValue;
257
258 if (i == rulesSize || rules[i] != Q_AND)
259 break;
260 ++i;
261 }
262
263 orExprTruthValue = orExprTruthValue || andExprTruthValue;
264
265 if (i == rulesSize || rules[i] != Q_OR)
266 break;
267 ++i;
268 }
269
270 if (orExprTruthValue)
271 return result;
272
273 ++result;
274
275 if (i == rulesSize)
276 return result;
277
278 i++; // Q_NEWRULE
279 }
280
281 Q_ASSERT(false);
282 return 0;
283}
284
285class QTranslatorPrivate : public QObjectPrivate
286{
287 Q_DECLARE_PUBLIC(QTranslator)
288public:
289 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96, Language = 0xa7 };
290
291 QTranslatorPrivate() :
292#if defined(QT_USE_MMAP)
293 used_mmap(0),
294#endif
295 unmapPointer(nullptr), unmapLength(0), resource(nullptr),
296 messageArray(nullptr), offsetArray(nullptr), contextArray(nullptr), numerusRulesArray(nullptr),
297 messageLength(0), offsetLength(0), contextLength(0), numerusRulesLength(0) {}
298
299#if defined(QT_USE_MMAP)
300 bool used_mmap : 1;
301#endif
302 char *unmapPointer; // used memory (mmap, new or resource file)
303 qsizetype unmapLength;
304
305 // The resource object in case we loaded the translations from a resource
306 std::unique_ptr<QResource> resource;
307
308 // used if the translator has dependencies
309 std::vector<std::unique_ptr<QTranslator>> subTranslators;
310
311 // Pointers and offsets into unmapPointer[unmapLength] array, or user
312 // provided data array
313 const uchar *messageArray;
314 const uchar *offsetArray;
315 const uchar *contextArray;
316 const uchar *numerusRulesArray;
317 uint messageLength;
318 uint offsetLength;
319 uint contextLength;
320 uint numerusRulesLength;
321
322 QString language;
323 QString filePath;
324
325 bool do_load(const QString &filename, const QString &directory);
326 bool do_load(const uchar *data, qsizetype len, const QString &directory);
327 QString do_translate(const char *context, const char *sourceText, const char *comment,
328 int n) const;
329 void clear();
330};
331
332/*!
333 \class QTranslator
334 \inmodule QtCore
335
336 \brief The QTranslator class provides internationalization support for text
337 output.
338
339 \ingroup i18n
340
341 An object of this class contains a set of translations from a
342 source language to a target language. QTranslator provides
343 functions to look up translations in a translation file.
344 Translation files are created using \l{Qt Linguist}.
345
346 The most common use of QTranslator is to: load a translation
347 file, and install it using QCoreApplication::installTranslator().
348
349 Here's an example \c main() function using the
350 QTranslator:
351
352 \snippet hellotrmain.cpp 0
353
354 Note that the translator must be created \e before the
355 application's widgets.
356
357 Most applications will never need to do anything else with this
358 class. The other functions provided by this class are useful for
359 applications that work on translator files.
360
361 \section1 Looking up Translations
362
363 It is possible to look up a translation using translate() (as tr()
364 and QCoreApplication::translate() do). The translate() function takes
365 up to three parameters:
366
367 \list
368 \li The \e context - usually the class name for the tr() caller.
369 \li The \e {source text} - usually the argument to tr().
370 \li The \e disambiguation - an optional string that helps disambiguate
371 different uses of the same text in the same context.
372 \endlist
373
374 For example, the "Cancel" in a dialog might have "Anuluj" when the
375 program runs in Polish (in this case the source text would be
376 "Cancel"). The context would (normally) be the dialog's class
377 name; there would normally be no comment, and the translated text
378 would be "Anuluj".
379
380 But it's not always so simple. The Spanish version of a printer
381 dialog with settings for two-sided printing and binding would
382 probably require both "Activado" and "Activada" as translations
383 for "Enabled". In this case the source text would be "Enabled" in
384 both cases, and the context would be the dialog's class name, but
385 the two items would have disambiguations such as "two-sided printing"
386 for one and "binding" for the other. The disambiguation enables the
387 translator to choose the appropriate gender for the Spanish version,
388 and enables Qt to distinguish between translations.
389
390 \section1 Using Multiple Translations
391
392 Multiple translation files can be installed in an application.
393 Translations are searched for in the reverse order in which they were
394 installed, so the most recently installed translation file is searched
395 for translations first and the earliest translation file is searched
396 last. The search stops as soon as a translation containing a matching
397 string is found.
398
399 This mechanism makes it possible for a specific translation to be
400 "selected" or given priority over the others; simply uninstall the
401 translator from the application by passing it to the
402 QCoreApplication::removeTranslator() function and reinstall it with
403 QCoreApplication::installTranslator(). It will then be the first
404 translation to be searched for matching strings.
405
406 \sa QCoreApplication::installTranslator(), QCoreApplication::removeTranslator(),
407 QObject::tr(), QCoreApplication::translate(), {I18N Example},
408 {Hello tr() Example}, {Arrow Pad Example}, {Troll Print Example}
409*/
410
411/*!
412 Constructs an empty message file object with parent \a parent that
413 is not connected to any file.
414*/
415
416QTranslator::QTranslator(QObject * parent)
417 : QObject(*new QTranslatorPrivate, parent)
418{
419}
420
421/*!
422 Destroys the object and frees any allocated resources.
423*/
424
425QTranslator::~QTranslator()
426{
427 if (QCoreApplication::instance())
428 QCoreApplication::removeTranslator(this);
429 Q_D(QTranslator);
430 d->clear();
431}
432
433/*!
434
435 Loads \a filename + \a suffix (".qm" if the \a suffix is not
436 specified), which may be an absolute file name or relative to \a
437 directory. Returns \c true if the translation is successfully loaded;
438 otherwise returns \c false.
439
440 If \a directory is not specified, the current directory is used
441 (i.e., as \l{QDir::}{currentPath()}).
442
443 The previous contents of this translator object are discarded.
444
445 If the file name does not exist, other file names are tried
446 in the following order:
447
448 \list 1
449 \li File name without \a suffix appended.
450 \li File name with text after a character in \a search_delimiters
451 stripped ("_." is the default for \a search_delimiters if it is
452 an empty string) and \a suffix.
453 \li File name stripped without \a suffix appended.
454 \li File name stripped further, etc.
455 \endlist
456
457 For example, an application running in the fr_CA locale
458 (French-speaking Canada) might call load("foo.fr_ca",
459 "/opt/foolib"). load() would then try to open the first existing
460 readable file from this list:
461
462 \list 1
463 \li \c /opt/foolib/foo.fr_ca.qm
464 \li \c /opt/foolib/foo.fr_ca
465 \li \c /opt/foolib/foo.fr.qm
466 \li \c /opt/foolib/foo.fr
467 \li \c /opt/foolib/foo.qm
468 \li \c /opt/foolib/foo
469 \endlist
470
471 Usually, it is better to use the QTranslator::load(const QLocale &,
472 const QString &, const QString &, const QString &, const QString &)
473 function instead, because it uses \l{QLocale::uiLanguages()} and not simply
474 the locale name, which refers to the formatting of dates and numbers and not
475 necessarily the UI language.
476*/
477
478bool QTranslator::load(const QString & filename, const QString & directory,
479 const QString & search_delimiters,
480 const QString & suffix)
481{
482 Q_D(QTranslator);
483 d->clear();
484
485 QString prefix;
486 if (QFileInfo(filename).isRelative()) {
487 prefix = directory;
488 if (prefix.length() && !prefix.endsWith(QLatin1Char('/')))
489 prefix += QLatin1Char('/');
490 }
491
492 const QString suffixOrDotQM = suffix.isNull() ? dotQmLiteral() : suffix;
493 QStringView fname(filename);
494 QString realname;
495 const QString delims = search_delimiters.isNull() ? QStringLiteral("_.") : search_delimiters;
496
497 for (;;) {
498 QFileInfo fi;
499
500 realname = prefix + fname + suffixOrDotQM;
501 fi.setFile(realname);
502 if (fi.isReadable() && fi.isFile())
503 break;
504
505 realname = prefix + fname;
506 fi.setFile(realname);
507 if (fi.isReadable() && fi.isFile())
508 break;
509
510 int rightmost = 0;
511 for (int i = 0; i < (int)delims.length(); i++) {
512 int k = fname.lastIndexOf(delims[i]);
513 if (k > rightmost)
514 rightmost = k;
515 }
516
517 // no truncations? fail
518 if (rightmost == 0)
519 return false;
520
521 fname.truncate(rightmost);
522 }
523
524 // realname is now the fully qualified name of a readable file.
525 return d->do_load(realname, directory);
526}
527
528bool QTranslatorPrivate::do_load(const QString &realname, const QString &directory)
529{
530 QTranslatorPrivate *d = this;
531 bool ok = false;
532
533 if (realname.startsWith(QLatin1Char(':'))) {
534 // If the translation is in a non-compressed resource file, the data is already in
535 // memory, so no need to use QFile to copy it again.
536 Q_ASSERT(!d->resource);
537 d->resource = std::make_unique<QResource>(realname);
538 if (resource->isValid() && resource->compressionAlgorithm() == QResource::NoCompression
539 && resource->size() >= MagicLength
540 && !memcmp(resource->data(), magic, MagicLength)) {
541 d->unmapLength = resource->size();
542 d->unmapPointer = reinterpret_cast<char *>(const_cast<uchar *>(resource->data()));
543#if defined(QT_USE_MMAP)
544 d->used_mmap = false;
545#endif
546 ok = true;
547 } else {
548 resource = nullptr;
549 }
550 }
551
552 if (!ok) {
553 QFile file(realname);
554 if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
555 return false;
556
557 qint64 fileSize = file.size();
558 if (fileSize < MagicLength || fileSize > std::numeric_limits<qsizetype>::max())
559 return false;
560
561 {
562 char magicBuffer[MagicLength];
563 if (MagicLength != file.read(magicBuffer, MagicLength)
564 || memcmp(magicBuffer, magic, MagicLength))
565 return false;
566 }
567
568 d->unmapLength = qsizetype(fileSize);
569
570#ifdef QT_USE_MMAP
571
572#ifndef MAP_FILE
573#define MAP_FILE 0
574#endif
575#ifndef MAP_FAILED
576#define MAP_FAILED reinterpret_cast<void *>(-1)
577#endif
578
579 int fd = file.handle();
580 if (fd >= 0) {
581 int protection = PROT_READ; // read-only memory
582 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
583 void *ptr = QT_MMAP(nullptr, d->unmapLength,// any address, whole file
584 protection, flags,
585 fd, 0); // from offset 0 of fd
586 if (ptr != MAP_FAILED) {
587 file.close();
588 d->used_mmap = true;
589 d->unmapPointer = static_cast<char *>(ptr);
590 ok = true;
591 }
592 }
593#endif // QT_USE_MMAP
594
595 if (!ok) {
596 d->unmapPointer = new (std::nothrow) char[d->unmapLength];
597 if (d->unmapPointer) {
598 file.seek(0);
599 qint64 readResult = file.read(d->unmapPointer, d->unmapLength);
600 if (readResult == qint64(unmapLength))
601 ok = true;
602 }
603 }
604 }
605
606 if (ok && d->do_load(reinterpret_cast<const uchar *>(d->unmapPointer), d->unmapLength, directory)) {
607 d->filePath = realname;
608 return true;
609 }
610
611#if defined(QT_USE_MMAP)
612 if (used_mmap) {
613 used_mmap = false;
614 munmap(unmapPointer, unmapLength);
615 } else
616#endif
617 if (!d->resource)
618 delete [] unmapPointer;
619
620 d->resource = nullptr;
621 d->unmapPointer = nullptr;
622 d->unmapLength = 0;
623
624 return false;
625}
626
627Q_NEVER_INLINE
628static bool is_readable_file(const QString &name)
629{
630 const QFileInfo fi(name);
631 return fi.isReadable() && fi.isFile();
632}
633
634static QString find_translation(const QLocale & locale,
635 const QString & filename,
636 const QString & prefix,
637 const QString & directory,
638 const QString & suffix)
639{
640 QString path;
641 if (QFileInfo(filename).isRelative()) {
642 path = directory;
643 if (!path.isEmpty() && !path.endsWith(QLatin1Char('/')))
644 path += QLatin1Char('/');
645 }
646 const QString suffixOrDotQM = suffix.isNull() ? dotQmLiteral() : suffix;
647
648 QString realname;
649 realname += path + filename + prefix; // using += in the hope for some reserve capacity
650 const int realNameBaseSize = realname.size();
651 QStringList fuzzyLocales;
652
653 // see http://www.unicode.org/reports/tr35/#LanguageMatching for inspiration
654
655 QStringList languages = locale.uiLanguages();
656#if defined(Q_OS_UNIX)
657 for (int i = languages.size()-1; i >= 0; --i) {
658 QString lang = languages.at(i);
659 QString lowerLang = lang.toLower();
660 if (lang != lowerLang)
661 languages.insert(i + 1, lowerLang);
662 }
663#endif
664
665 // try explicit locales names first
666 for (QString localeName : qAsConst(languages)) {
667 localeName.replace(QLatin1Char('-'), QLatin1Char('_'));
668
669 realname += localeName + suffixOrDotQM;
670 if (is_readable_file(realname))
671 return realname;
672
673 realname.truncate(realNameBaseSize + localeName.size());
674 if (is_readable_file(realname))
675 return realname;
676
677 realname.truncate(realNameBaseSize);
678 fuzzyLocales.append(localeName);
679 }
680
681 // start guessing
682 for (const QString &fuzzyLocale : qAsConst(fuzzyLocales)) {
683 QStringView localeName(fuzzyLocale);
684 for (;;) {
685 int rightmost = localeName.lastIndexOf(QLatin1Char('_'));
686 // no truncations? fail
687 if (rightmost <= 0)
688 break;
689 localeName.truncate(rightmost);
690
691 realname += localeName + suffixOrDotQM;
692 if (is_readable_file(realname))
693 return realname;
694
695 realname.truncate(realNameBaseSize + localeName.size());
696 if (is_readable_file(realname))
697 return realname;
698
699 realname.truncate(realNameBaseSize);
700 }
701 }
702
703 const int realNameBaseSizeFallbacks = path.size() + filename.size();
704
705 // realname == path + filename + prefix;
706 if (!suffix.isNull()) {
707 realname.replace(realNameBaseSizeFallbacks, prefix.size(), suffix);
708 // realname == path + filename;
709 if (is_readable_file(realname))
710 return realname;
711 realname.replace(realNameBaseSizeFallbacks, suffix.size(), prefix);
712 }
713
714 // realname == path + filename + prefix;
715 if (is_readable_file(realname))
716 return realname;
717
718 realname.truncate(realNameBaseSizeFallbacks);
719 // realname == path + filename;
720 if (is_readable_file(realname))
721 return realname;
722
723 realname.truncate(0);
724 return realname;
725}
726
727/*!
728 \since 4.8
729
730 Loads \a filename + \a prefix + \l{QLocale::uiLanguages()}{ui language
731 name} + \a suffix (".qm" if the \a suffix is not specified), which may be
732 an absolute file name or relative to \a directory. Returns \c true if the
733 translation is successfully loaded; otherwise returns \c false.
734
735 The previous contents of this translator object are discarded.
736
737 If the file name does not exist, other file names are tried
738 in the following order:
739
740 \list 1
741 \li File name without \a suffix appended.
742 \li File name with ui language part after a "_" character stripped and \a suffix.
743 \li File name with ui language part stripped without \a suffix appended.
744 \li File name with ui language part stripped further, etc.
745 \endlist
746
747 For example, an application running in the \a locale with the following
748 \l{QLocale::uiLanguages()}{ui languages} - "es", "fr-CA", "de" might call
749 load(QLocale(), "foo", ".", "/opt/foolib", ".qm"). load() would
750 replace '-' (dash) with '_' (underscore) in the ui language and then try to
751 open the first existing readable file from this list:
752
753 \list 1
754 \li \c /opt/foolib/foo.es.qm
755 \li \c /opt/foolib/foo.es
756 \li \c /opt/foolib/foo.fr_CA.qm
757 \li \c /opt/foolib/foo.fr_CA
758 \li \c /opt/foolib/foo.de.qm
759 \li \c /opt/foolib/foo.de
760 \li \c /opt/foolib/foo.fr.qm
761 \li \c /opt/foolib/foo.fr
762 \li \c /opt/foolib/foo.qm
763 \li \c /opt/foolib/foo.
764 \li \c /opt/foolib/foo
765 \endlist
766
767 On operating systems where file system is case sensitive, QTranslator also
768 tries to load a lower-cased version of the locale name.
769*/
770bool QTranslator::load(const QLocale & locale,
771 const QString & filename,
772 const QString & prefix,
773 const QString & directory,
774 const QString & suffix)
775{
776 Q_D(QTranslator);
777 d->clear();
778 QString fname = find_translation(locale, filename, prefix, directory, suffix);
779 return !fname.isEmpty() && d->do_load(fname, directory);
780}
781
782/*!
783 \overload load()
784
785 Loads the QM file data \a data of length \a len into the
786 translator.
787
788 The data is not copied. The caller must be able to guarantee that \a data
789 will not be deleted or modified.
790
791 \a directory is only used to specify the base directory when loading the dependencies
792 of a QM file. If the file does not have dependencies, this argument is ignored.
793*/
794bool QTranslator::load(const uchar *data, int len, const QString &directory)
795{
796 Q_D(QTranslator);
797 d->clear();
798
799 if (!data || len < MagicLength || memcmp(data, magic, MagicLength))
800 return false;
801
802 return d->do_load(data, len, directory);
803}
804
805static quint8 read8(const uchar *data)
806{
807 return qFromBigEndian<quint8>(data);
808}
809
810static quint16 read16(const uchar *data)
811{
812 return qFromBigEndian<quint16>(data);
813}
814
815static quint32 read32(const uchar *data)
816{
817 return qFromBigEndian<quint32>(data);
818}
819
820bool QTranslatorPrivate::do_load(const uchar *data, qsizetype len, const QString &directory)
821{
822 bool ok = true;
823 const uchar *end = data + len;
824
825 data += MagicLength;
826
827 QStringList dependencies;
828 while (data < end - 5) {
829 quint8 tag = read8(data++);
830 quint32 blockLen = read32(data);
831 data += 4;
832 if (!tag || !blockLen)
833 break;
834 if (quint32(end - data) < blockLen) {
835 ok = false;
836 break;
837 }
838
839 if (tag == QTranslatorPrivate::Language) {
840 language = QString::fromUtf8((const char *)data, blockLen);
841 } else if (tag == QTranslatorPrivate::Contexts) {
842 contextArray = data;
843 contextLength = blockLen;
844 } else if (tag == QTranslatorPrivate::Hashes) {
845 offsetArray = data;
846 offsetLength = blockLen;
847 } else if (tag == QTranslatorPrivate::Messages) {
848 messageArray = data;
849 messageLength = blockLen;
850 } else if (tag == QTranslatorPrivate::NumerusRules) {
851 numerusRulesArray = data;
852 numerusRulesLength = blockLen;
853 } else if (tag == QTranslatorPrivate::Dependencies) {
854 QDataStream stream(QByteArray::fromRawData((const char*)data, blockLen));
855 QString dep;
856 while (!stream.atEnd()) {
857 stream >> dep;
858 dependencies.append(dep);
859 }
860 }
861
862 data += blockLen;
863 }
864
865 if (ok && !isValidNumerusRules(numerusRulesArray, numerusRulesLength))
866 ok = false;
867 if (ok) {
868 subTranslators.reserve(std::size_t(dependencies.size()));
869 for (const QString &dependency : std::as_const(dependencies)) {
870 auto translator = std::make_unique<QTranslator>();
871 ok = translator->load(dependency, directory);
872 if (!ok)
873 break;
874 subTranslators.push_back(std::move(translator));
875 }
876
877 // In case some dependencies fail to load, unload all the other ones too.
878 if (!ok)
879 subTranslators.clear();
880 }
881
882 if (!ok) {
883 messageArray = nullptr;
884 contextArray = nullptr;
885 offsetArray = nullptr;
886 numerusRulesArray = nullptr;
887 messageLength = 0;
888 contextLength = 0;
889 offsetLength = 0;
890 numerusRulesLength = 0;
891 }
892
893 return ok;
894}
895
896static QString getMessage(const uchar *m, const uchar *end, const char *context,
897 const char *sourceText, const char *comment, uint numerus)
898{
899 const uchar *tn = nullptr;
900 uint tn_length = 0;
901 const uint sourceTextLen = uint(strlen(sourceText));
902 const uint contextLen = uint(strlen(context));
903 const uint commentLen = uint(strlen(comment));
904
905 for (;;) {
906 uchar tag = 0;
907 if (m < end)
908 tag = read8(m++);
909 switch ((Tag)tag) {
910 case Tag_End:
911 goto end;
912 case Tag_Translation: {
913 int len = read32(m);
914 if (len & 1)
915 return QString();
916 m += 4;
917 if (!numerus--) {
918 tn_length = len;
919 tn = m;
920 }
921 m += len;
922 break;
923 }
924 case Tag_Obsolete1:
925 m += 4;
926 break;
927 case Tag_SourceText: {
928 quint32 len = read32(m);
929 m += 4;
930 if (!match(m, len, sourceText, sourceTextLen))
931 return QString();
932 m += len;
933 }
934 break;
935 case Tag_Context: {
936 quint32 len = read32(m);
937 m += 4;
938 if (!match(m, len, context, contextLen))
939 return QString();
940 m += len;
941 }
942 break;
943 case Tag_Comment: {
944 quint32 len = read32(m);
945 m += 4;
946 if (*m && !match(m, len, comment, commentLen))
947 return QString();
948 m += len;
949 }
950 break;
951 default:
952 return QString();
953 }
954 }
955end:
956 if (!tn)
957 return QString();
958 QString str(tn_length / 2, Qt::Uninitialized);
959 qFromBigEndian<ushort>(tn, str.length(), str.data());
960 return str;
961}
962
963QString QTranslatorPrivate::do_translate(const char *context, const char *sourceText,
964 const char *comment, int n) const
965{
966 if (context == nullptr)
967 context = "";
968 if (sourceText == nullptr)
969 sourceText = "";
970 if (comment == nullptr)
971 comment = "";
972
973 uint numerus = 0;
974 size_t numItems = 0;
975
976 if (!offsetLength)
977 goto searchDependencies;
978
979 /*
980 Check if the context belongs to this QTranslator. If many
981 translators are installed, this step is necessary.
982 */
983 if (contextLength) {
984 quint16 hTableSize = read16(contextArray);
985 uint g = elfHash(context) % hTableSize;
986 const uchar *c = contextArray + 2 + (g << 1);
987 quint16 off = read16(c);
988 c += 2;
989 if (off == 0)
990 return QString();
991 c = contextArray + (2 + (hTableSize << 1) + (off << 1));
992
993 const uint contextLen = uint(strlen(context));
994 for (;;) {
995 quint8 len = read8(c++);
996 if (len == 0)
997 return QString();
998 if (match(c, len, context, contextLen))
999 break;
1000 c += len;
1001 }
1002 }
1003
1004 numItems = offsetLength / (2 * sizeof(quint32));
1005 if (!numItems)
1006 goto searchDependencies;
1007
1008 if (n >= 0)
1009 numerus = numerusHelper(n, numerusRulesArray, numerusRulesLength);
1010
1011 for (;;) {
1012 quint32 h = 0;
1013 elfHash_continue(sourceText, h);
1014 elfHash_continue(comment, h);
1015 elfHash_finish(h);
1016
1017 const uchar *start = offsetArray;
1018 const uchar *end = start + ((numItems - 1) << 3);
1019 while (start <= end) {
1020 const uchar *middle = start + (((end - start) >> 4) << 3);
1021 uint hash = read32(middle);
1022 if (h == hash) {
1023 start = middle;
1024 break;
1025 } else if (hash < h) {
1026 start = middle + 8;
1027 } else {
1028 end = middle - 8;
1029 }
1030 }
1031
1032 if (start <= end) {
1033 // go back on equal key
1034 while (start != offsetArray && read32(start) == read32(start - 8))
1035 start -= 8;
1036
1037 while (start < offsetArray + offsetLength) {
1038 quint32 rh = read32(start);
1039 start += 4;
1040 if (rh != h)
1041 break;
1042 quint32 ro = read32(start);
1043 start += 4;
1044 QString tn = getMessage(messageArray + ro, messageArray + messageLength, context,
1045 sourceText, comment, numerus);
1046 if (!tn.isNull())
1047 return tn;
1048 }
1049 }
1050 if (!comment[0])
1051 break;
1052 comment = "";
1053 }
1054
1055searchDependencies:
1056 for (const auto &translator : subTranslators) {
1057 QString tn = translator->translate(context, sourceText, comment, n);
1058 if (!tn.isNull())
1059 return tn;
1060 }
1061 return QString();
1062}
1063
1064/*
1065 Empties this translator of all contents.
1066
1067 This function works with stripped translator files.
1068*/
1069
1070void QTranslatorPrivate::clear()
1071{
1072 Q_Q(QTranslator);
1073 if (unmapPointer && unmapLength) {
1074#if defined(QT_USE_MMAP)
1075 if (used_mmap) {
1076 used_mmap = false;
1077 munmap(unmapPointer, unmapLength);
1078 } else
1079#endif
1080 if (!resource)
1081 delete [] unmapPointer;
1082 }
1083
1084 resource = nullptr;
1085 unmapPointer = nullptr;
1086 unmapLength = 0;
1087 messageArray = nullptr;
1088 contextArray = nullptr;
1089 offsetArray = nullptr;
1090 numerusRulesArray = nullptr;
1091 messageLength = 0;
1092 contextLength = 0;
1093 offsetLength = 0;
1094 numerusRulesLength = 0;
1095
1096 subTranslators.clear();
1097
1098 language.clear();
1099 filePath.clear();
1100
1101 if (QCoreApplicationPrivate::isTranslatorInstalled(q))
1102 QCoreApplication::postEvent(QCoreApplication::instance(),
1103 new QEvent(QEvent::LanguageChange));
1104}
1105
1106/*!
1107 Returns the translation for the key (\a context, \a sourceText,
1108 \a disambiguation). If none is found, also tries (\a context, \a
1109 sourceText, ""). If that still fails, returns a null string.
1110
1111 \note Incomplete translations may result in unexpected behavior:
1112 If no translation for (\a context, \a sourceText, "")
1113 is provided, the method might in this case actually return a
1114 translation for a different \a disambiguation.
1115
1116 If \a n is not -1, it is used to choose an appropriate form for
1117 the translation (e.g. "%n file found" vs. "%n files found").
1118
1119 If you need to programatically insert translations into a
1120 QTranslator, this function can be reimplemented.
1121
1122 \sa load()
1123*/
1124QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation,
1125 int n) const
1126{
1127 Q_D(const QTranslator);
1128 return d->do_translate(context, sourceText, disambiguation, n);
1129}
1130
1131/*!
1132 Returns \c true if this translator is empty, otherwise returns \c false.
1133 This function works with stripped and unstripped translation files.
1134*/
1135bool QTranslator::isEmpty() const
1136{
1137 Q_D(const QTranslator);
1138 return !d->messageArray && !d->offsetArray && !d->contextArray
1139 && d->subTranslators.empty();
1140}
1141
1142/*!
1143 \since 5.15
1144
1145 Returns the target language as stored in the translation file.
1146 */
1147QString QTranslator::language() const
1148{
1149 Q_D(const QTranslator);
1150 return d->language;
1151}
1152
1153/*!
1154 \since 5.15
1155
1156 Returns the path of the loaded translation file.
1157
1158 The file path is empty if no translation was loaded yet,
1159 the loading failed, or if the translation was not loaded
1160 from a file.
1161 */
1162QString QTranslator::filePath() const
1163{
1164 Q_D(const QTranslator);
1165 return d->filePath;
1166}
1167
1168QT_END_NAMESPACE
1169
1170#include "moc_qtranslator.cpp"
1171
1172#endif // QT_NO_TRANSLATION
1173