e5fcad3 by Lars Knoll at 2009-03-23 1
/****************************************************************************
2
**
89c08c0 by Jason McDonald at 2012-01-11 3
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
04e3b30 by Jason McDonald at 2009-09-09 4
** All rights reserved.
858c70f by Jason McDonald at 2009-06-16 5
** Contact: Nokia Corporation (qt-info@nokia.com)
e5fcad3 by Lars Knoll at 2009-03-23 6
**
7
** This file is part of the QtCore module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** GNU Lesser General Public License Usage
1eea52e by Jyri Tahtela at 2011-05-13 11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
e5fcad3 by Lars Knoll at 2009-03-23 17
**
04e3b30 by Jason McDonald at 2009-09-09 18
** In addition, as a special exception, Nokia gives you certain additional
1eea52e by Jyri Tahtela at 2011-05-13 19
** rights. These rights are described in the Nokia Qt LGPL Exception
04e3b30 by Jason McDonald at 2009-09-09 20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
e5fcad3 by Lars Knoll at 2009-03-23 21
**
1eea52e by Jyri Tahtela at 2011-05-13 22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
309db73 by Jason McDonald at 2009-08-31 33
**
34
**
35
**
36
**
e5fcad3 by Lars Knoll at 2009-03-23 37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include "qplatformdefs.h"
43
44
#include "qtranslator.h"
45
46
#ifndef QT_NO_TRANSLATION
47
48
#include "qfileinfo.h"
49
#include "qstring.h"
18508ec by Denis Dzyubenko at 2011-02-23 50
#include "qstringlist.h"
e5fcad3 by Lars Knoll at 2009-03-23 51
#include "qcoreapplication.h"
52
#include "qcoreapplication_p.h"
53
#include "qdatastream.h"
5dec90f by Shane Kearns at 2011-10-13 54
#include "qdir.h"
e5fcad3 by Lars Knoll at 2009-03-23 55
#include "qfile.h"
56
#include "qmap.h"
57
#include "qalgorithms.h"
58
#include "qhash.h"
59
#include "qtranslator_p.h"
18508ec by Denis Dzyubenko at 2011-02-23 60
#include "qlocale.h"
e5fcad3 by Lars Knoll at 2009-03-23 61
31d2975 by Rolland Dudemaine at 2011-02-22 62
#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN) && !defined(Q_OS_INTEGRITY)
e5fcad3 by Lars Knoll at 2009-03-23 63
#define QT_USE_MMAP
29302d2 by Thiago Macieira at 2009-07-02 64
#include "private/qcore_unix_p.h"
e5fcad3 by Lars Knoll at 2009-03-23 65
#endif
66
5dec90f by Shane Kearns at 2011-10-13 67
#ifdef Q_OS_SYMBIAN
68
#include "private/qcore_symbian_p.h"
69
#endif
70
e5fcad3 by Lars Knoll at 2009-03-23 71
// most of the headers below are already included in qplatformdefs.h
72
// also this lacks Large File support but that's probably irrelevant
73
#if defined(QT_USE_MMAP)
74
// for mmap
75
#include <sys/mman.h>
76
#include <errno.h>
77
#endif
78
79
#include <stdlib.h>
80
81
#include "qobject_p.h"
82
83
QT_BEGIN_NAMESPACE
84
85
enum Tag { Tag_End = 1, Tag_SourceText16, Tag_Translation, Tag_Context16, Tag_Obsolete1,
86
           Tag_SourceText, Tag_Context, Tag_Comment, Tag_Obsolete2 };
87
/*
88
$ mcookie
89
3cb86418caef9c95cd211cbf60a1bddd
90
$
91
*/
92
93
// magic number for the file
94
static const int MagicLength = 16;
95
static const uchar magic[MagicLength] = {
96
    0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
97
    0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
98
};
99
100
static bool match(const uchar* found, const char* target, uint len)
101
{
102
    // catch the case if \a found has a zero-terminating symbol and \a len includes it.
103
    // (normalize it to be without the zero-terminating symbol)
104
    if (len > 0 && found[len-1] == '\0')
105
        --len;
aeb5fd4 by Oswald Buddenhagen at 2009-03-27 106
    return (memcmp(found, target, len) == 0 && target[len] == '\0');
e5fcad3 by Lars Knoll at 2009-03-23 107
}
108
109
static uint elfHash(const char *name)
110
{
111
    const uchar *k;
112
    uint h = 0;
113
    uint g;
114
115
    if (name) {
116
        k = (const uchar *) name;
117
        while (*k) {
118
            h = (h << 4) + *k++;
119
            if ((g = (h & 0xf0000000)) != 0)
120
                h ^= g >> 24;
121
            h &= ~g;
122
        }
123
    }
124
    if (!h)
125
        h = 1;
126
    return h;
127
}
128
129
static int numerusHelper(int n, const uchar *rules, int rulesSize)
130
{
131
#define CHECK_RANGE \
132
    do { \
133
        if (i >= rulesSize) \
134
            return -1; \
135
    } while (0)
136
137
    int result = 0;
138
    int i = 0;
139
140
    if (rulesSize == 0)
141
        return 0;
142
143
    for (;;) {
144
        bool orExprTruthValue = false;
145
146
        for (;;) {
147
            bool andExprTruthValue = true;
148
149
            for (;;) {
150
                bool truthValue = true;
151
152
                CHECK_RANGE;
153
                int opcode = rules[i++];
154
155
                int leftOperand = n;
156
                if (opcode & Q_MOD_10) {
157
                    leftOperand %= 10;
158
                } else if (opcode & Q_MOD_100) {
159
                    leftOperand %= 100;
f1e471b by Oswald Buddenhagen at 2009-06-03 160
                } else if (opcode & Q_LEAD_1000) {
161
                    while (leftOperand >= 1000)
162
                        leftOperand /= 1000;
e5fcad3 by Lars Knoll at 2009-03-23 163
                }
164
165
                int op = opcode & Q_OP_MASK;
166
167
                CHECK_RANGE;
168
                int rightOperand = rules[i++];
169
170
                switch (op) {
171
                default:
172
                    return -1;
173
                case Q_EQ:
174
                    truthValue = (leftOperand == rightOperand);
175
                    break;
176
                case Q_LT:
177
                    truthValue = (leftOperand < rightOperand);
178
                    break;
179
                case Q_LEQ:
180
                    truthValue = (leftOperand <= rightOperand);
181
                    break;
182
		case Q_BETWEEN:
183
                    int bottom = rightOperand;
184
                    CHECK_RANGE;
185
                    int top = rules[i++];
186
                    truthValue = (leftOperand >= bottom && leftOperand <= top);
187
                }
188
189
                if (opcode & Q_NOT)
190
                    truthValue = !truthValue;
191
192
                andExprTruthValue = andExprTruthValue && truthValue;
193
194
                if (i == rulesSize || rules[i] != Q_AND)
195
                    break;
196
                ++i;
197
            }
198
199
            orExprTruthValue = orExprTruthValue || andExprTruthValue;
200
201
            if (i == rulesSize || rules[i] != Q_OR)
202
                break;
203
            ++i;
204
        }
205
206
        if (orExprTruthValue)
207
            return result;
208
209
        ++result;
210
211
        if (i == rulesSize)
212
            return result;
213
214
        if (rules[i++] != Q_NEWRULE)
215
            break;
216
    }
217
    return -1;
218
}
219
220
class QTranslatorPrivate : public QObjectPrivate
221
{
222
    Q_DECLARE_PUBLIC(QTranslator)
223
public:
224
    enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
225
226
    QTranslatorPrivate()
227
        : used_mmap(0), unmapPointer(0), unmapLength(0),
228
          messageArray(0), offsetArray(0), contextArray(0), numerusRulesArray(0),
229
          messageLength(0), offsetLength(0), contextLength(0), numerusRulesLength(0) {}
230
231
    // for mmap'ed files, this is what needs to be unmapped.
232
    bool used_mmap : 1;
233
    char *unmapPointer;
234
    unsigned int unmapLength;
235
236
    // for squeezed but non-file data, this is what needs to be deleted
237
    const uchar *messageArray;
238
    const uchar *offsetArray;
239
    const uchar *contextArray;
240
    const uchar *numerusRulesArray;
241
    uint messageLength;
242
    uint offsetLength;
243
    uint contextLength;
244
    uint numerusRulesLength;
245
18508ec by Denis Dzyubenko at 2011-02-23 246
    bool do_load(const QString &filename);
e5fcad3 by Lars Knoll at 2009-03-23 247
    bool do_load(const uchar *data, int len);
248
    QString do_translate(const char *context, const char *sourceText, const char *comment,
249
                         int n) const;
250
    void clear();
251
};
252
253
/*!
254
    \class QTranslator
255
256
    \brief The QTranslator class provides internationalization support for text
257
    output.
258
259
    \ingroup i18n
260
261
    An object of this class contains a set of translations from a
262
    source language to a target language. QTranslator provides
263
    functions to look up translations in a translation file.
264
    Translation files are created using \l{Qt Linguist}.
265
266
    The most common use of QTranslator is to: load a translation
267
    file, install it using QApplication::installTranslator(), and use
268
    it via QObject::tr(). Here's the \c main() function from the
269
    \l{linguist/hellotr}{Hello tr()} example:
270
271
    \snippet examples/linguist/hellotr/main.cpp 2
272
273
    Note that the translator must be created \e before the
274
    application's widgets.
275
276
    Most applications will never need to do anything else with this
277
    class. The other functions provided by this class are useful for
278
    applications that work on translator files.
279
280
    \section1 Looking up Translations
281
282
    It is possible to look up a translation using translate() (as tr()
283
    and QApplication::translate() do). The translate() function takes
284
    up to three parameters:
285
286
    \list
287
    \o The \e context - usually the class name for the tr() caller.
288
    \o The \e {source text} - usually the argument to tr().
289
    \o The \e disambiguation - an optional string that helps disambiguate
290
       different uses of the same text in the same context.
291
    \endlist
292
293
    For example, the "Cancel" in a dialog might have "Anuluj" when the
294
    program runs in Polish (in this case the source text would be
295
    "Cancel"). The context would (normally) be the dialog's class
296
    name; there would normally be no comment, and the translated text
297
    would be "Anuluj".
298
299
    But it's not always so simple. The Spanish version of a printer
300
    dialog with settings for two-sided printing and binding would
301
    probably require both "Activado" and "Activada" as translations
302
    for "Enabled". In this case the source text would be "Enabled" in
303
    both cases, and the context would be the dialog's class name, but
304
    the two items would have disambiguations such as "two-sided printing"
305
    for one and "binding" for the other. The disambiguation enables the
306
    translator to choose the appropriate gender for the Spanish version,
307
    and enables Qt to distinguish between translations.
308
309
    \section1 Using Multiple Translations
310
311
    Multiple translation files can be installed in an application.
312
    Translations are searched for in the reverse order in which they were
313
    installed, so the most recently installed translation file is searched
314
    for translations first and the earliest translation file is searched
315
    last. The search stops as soon as a translation containing a matching
316
    string is found.
317
318
    This mechanism makes it possible for a specific translation to be
319
    "selected" or given priority over the others; simply uninstall the
320
    translator from the application by passing it to the
321
    QApplication::removeTranslator() function and reinstall it with
322
    QApplication::installTranslator(). It will then be the first
323
    translation to be searched for matching strings.
324
325
    \sa QApplication::installTranslator(), QApplication::removeTranslator(),
326
        QObject::tr(), QApplication::translate(), {I18N Example},
327
        {Hello tr() Example}, {Arrow Pad Example}, {Troll Print Example}
328
*/
329
330
/*!
331
    Constructs an empty message file object with parent \a parent that
332
    is not connected to any file.
333
*/
334
335
QTranslator::QTranslator(QObject * parent)
336
    : QObject(*new QTranslatorPrivate, parent)
337
{
338
}
339
340
#ifdef QT3_SUPPORT
341
/*!
342
    \overload QTranslator()
343
    \obsolete
344
 */
345
QTranslator::QTranslator(QObject * parent, const char * name)
346
    : QObject(*new QTranslatorPrivate, parent)
347
{
348
    setObjectName(QString::fromAscii(name));
349
}
350
#endif
351
352
/*!
353
    Destroys the object and frees any allocated resources.
354
*/
355
356
QTranslator::~QTranslator()
357
{
358
    if (QCoreApplication::instance())
4193c81 by David Faure at 2009-05-29 359
        QCoreApplication::removeTranslator(this);
e5fcad3 by Lars Knoll at 2009-03-23 360
    Q_D(QTranslator);
361
    d->clear();
362
}
363
364
/*!
31f3040 by Geir Vattekar at 2011-03-07 365
366
    Loads \a filename + \a suffix (".qm" if the \a suffix is not
367
    specified), which may be an absolute file name or relative to \a
368
    directory. Returns true if the translation is successfully loaded;
369
    otherwise returns false.
370
371
    If \a directory is not specified, the directory of the
372
    application's executable is used (i.e., as
373
    \l{QCoreApplication::}{applicationDirPath()}). 
e5fcad3 by Lars Knoll at 2009-03-23 374
375
    The previous contents of this translator object are discarded.
376
377
    If the file name does not exist, other file names are tried
378
    in the following order:
379
380
    \list 1
381
    \o File name without \a suffix appended.
382
    \o File name with text after a character in \a search_delimiters
383
       stripped ("_." is the default for \a search_delimiters if it is
384
       an empty string) and \a suffix.
385
    \o File name stripped without \a suffix appended.
386
    \o File name stripped further, etc.
387
    \endlist
388
389
    For example, an application running in the fr_CA locale
390
    (French-speaking Canada) might call load("foo.fr_ca",
391
    "/opt/foolib"). load() would then try to open the first existing
392
    readable file from this list:
393
394
    \list 1
395
    \o \c /opt/foolib/foo.fr_ca.qm
396
    \o \c /opt/foolib/foo.fr_ca
397
    \o \c /opt/foolib/foo.fr.qm
398
    \o \c /opt/foolib/foo.fr
399
    \o \c /opt/foolib/foo.qm
400
    \o \c /opt/foolib/foo
401
    \endlist
402
*/
403
404
bool QTranslator::load(const QString & filename, const QString & directory,
405
                       const QString & search_delimiters,
406
                       const QString & suffix)
407
{
408
    Q_D(QTranslator);
409
    d->clear();
410
865ad50 by Shane Kearns at 2011-11-07 411
    QString fname = filename;
e5fcad3 by Lars Knoll at 2009-03-23 412
    QString prefix;
413
    if (QFileInfo(filename).isRelative()) {
5dec90f by Shane Kearns at 2011-10-13 414
#ifdef Q_OS_SYMBIAN
865ad50 by Shane Kearns at 2011-11-07 415
        //TFindFile doesn't like path in the filename
416
        QString dir(directory);
417
        int slash = filename.lastIndexOf(QLatin1Char('/'));
418
        slash = qMax(slash, filename.lastIndexOf(QLatin1Char('\\')));
419
        if (slash >=0) {
420
            //so move the path component into the directory prefix
421
            if (dir.isEmpty())
422
                dir = filename.left(slash + 1);
423
            else
424
                dir = dir + QLatin1Char('/') + filename.left(slash + 1);
425
            fname = fname.mid(slash + 1);
426
        }
427
        if (dir.isEmpty())
5dec90f by Shane Kearns at 2011-10-13 428
            prefix = QCoreApplication::applicationDirPath();
429
        else
865ad50 by Shane Kearns at 2011-11-07 430
            prefix = QFileInfo(dir).absoluteFilePath(); //TFindFile doesn't like dirty paths
5dec90f by Shane Kearns at 2011-10-13 431
        if (prefix.length() > 2 && prefix.at(1) == QLatin1Char(':') && prefix.at(0).isLetter())
432
            prefix[0] = QLatin1Char('Y');
433
#else
e5fcad3 by Lars Knoll at 2009-03-23 434
        prefix = directory;
5dec90f by Shane Kearns at 2011-10-13 435
#endif
436
        if (prefix.length() && !prefix.endsWith(QLatin1Char('/')))
437
            prefix += QLatin1Char('/');
e5fcad3 by Lars Knoll at 2009-03-23 438
    }
439
5dec90f by Shane Kearns at 2011-10-13 440
#ifdef Q_OS_SYMBIAN
441
    QString nativePrefix = QDir::toNativeSeparators(prefix);
442
#endif
443
e5fcad3 by Lars Knoll at 2009-03-23 444
    QString realname;
445
    QString delims;
446
    delims = search_delimiters.isNull() ? QString::fromLatin1("_.") : search_delimiters;
447
448
    for (;;) {
449
        QFileInfo fi;
450
5dec90f by Shane Kearns at 2011-10-13 451
#ifdef Q_OS_SYMBIAN
452
        //search for translations on other drives, e.g. Qt may be in Z, while app is in C
453
        //note this uses symbian search rules, i.e. y:->a:, followed by z:
454
        TFindFile finder(qt_s60GetRFs());
455
        QString fname2 = fname + (suffix.isNull() ? QString::fromLatin1(".qm") : suffix);
456
        TInt err = finder.FindByDir(
457
            qt_QString2TPtrC(fname2),
458
            qt_QString2TPtrC(nativePrefix));
459
        if (err != KErrNone)
460
            err = finder.FindByDir(qt_QString2TPtrC(fname), qt_QString2TPtrC(nativePrefix));
461
        if (err == KErrNone) {
462
            fi.setFile(qt_TDesC2QString(finder.File()));
463
            realname = fi.canonicalFilePath();
464
            if (fi.isReadable() && fi.isFile())
465
                break;
466
        }
467
#endif
468
e5fcad3 by Lars Knoll at 2009-03-23 469
        realname = prefix + fname + (suffix.isNull() ? QString::fromLatin1(".qm") : suffix);
470
        fi.setFile(realname);
67ff950 by David Sansome at 2010-03-24 471
        if (fi.isReadable() && fi.isFile())
e5fcad3 by Lars Knoll at 2009-03-23 472
            break;
473
474
        realname = prefix + fname;
475
        fi.setFile(realname);
67ff950 by David Sansome at 2010-03-24 476
        if (fi.isReadable() && fi.isFile())
e5fcad3 by Lars Knoll at 2009-03-23 477
            break;
478
479
        int rightmost = 0;
480
        for (int i = 0; i < (int)delims.length(); i++) {
481
            int k = fname.lastIndexOf(delims[i]);
482
            if (k > rightmost)
483
                rightmost = k;
484
        }
485
486
        // no truncations? fail
487
        if (rightmost == 0)
488
            return false;
489
490
        fname.truncate(rightmost);
491
    }
492
493
    // realname is now the fully qualified name of a readable file.
18508ec by Denis Dzyubenko at 2011-02-23 494
    return d->do_load(realname);
495
}
e5fcad3 by Lars Knoll at 2009-03-23 496
18508ec by Denis Dzyubenko at 2011-02-23 497
bool QTranslatorPrivate::do_load(const QString &realname)
498
{
499
    QTranslatorPrivate *d = this;
e5fcad3 by Lars Knoll at 2009-03-23 500
    bool ok = false;
501
502
#ifdef QT_USE_MMAP
503
504
#ifndef MAP_FILE
505
#define MAP_FILE 0
506
#endif
507
#ifndef MAP_FAILED
508
#define MAP_FAILED -1
509
#endif
510
511
    int fd = -1;
512
    if (!realname.startsWith(QLatin1Char(':')))
513
        fd = QT_OPEN(QFile::encodeName(realname), O_RDONLY,
514
#if defined(Q_OS_WIN)
515
                     _S_IREAD | _S_IWRITE
516
#else
517
                     0666
518
#endif
519
            );
520
    if (fd >= 0) {
521
        QT_STATBUF st;
522
        if (!QT_FSTAT(fd, &st)) {
523
            char *ptr;
524
            ptr = reinterpret_cast<char *>(
525
                mmap(0, st.st_size,             // any address, whole file
526
                     PROT_READ,                 // read-only memory
527
                     MAP_FILE | MAP_PRIVATE,    // swap-backed map from file
528
                     fd, 0));                   // from offset 0 of fd
529
            if (ptr && ptr != reinterpret_cast<char *>(MAP_FAILED)) {
530
                d->used_mmap = true;
531
                d->unmapPointer = ptr;
532
                d->unmapLength = st.st_size;
533
                ok = true;
534
            }
535
        }
536
        ::close(fd);
537
    }
538
#endif // QT_USE_MMAP
539
540
    if (!ok) {
541
        QFile file(realname);
542
        d->unmapLength = file.size();
543
        if (!d->unmapLength)
544
            return false;
545
        d->unmapPointer = new char[d->unmapLength];
546
547
        if (file.open(QIODevice::ReadOnly))
548
            ok = (d->unmapLength == (uint)file.read(d->unmapPointer, d->unmapLength));
549
550
        if (!ok) {
551
            delete [] d->unmapPointer;
552
            d->unmapPointer = 0;
553
            d->unmapLength = 0;
554
            return false;
555
        }
556
    }
557
558
    return d->do_load(reinterpret_cast<const uchar *>(d->unmapPointer), d->unmapLength);
559
}
560
18508ec by Denis Dzyubenko at 2011-02-23 561
static QString find_translation(const QLocale & locale,
562
                                const QString & filename,
563
                                const QString & prefix,
564
                                const QString & directory,
565
                                const QString & suffix)
566
{
567
    QString path;
568
    if (QFileInfo(filename).isRelative()) {
569
        path = directory;
570
        if (!path.isEmpty() && !path.endsWith(QLatin1Char('/')))
571
            path += QLatin1Char('/');
572
    }
573
574
    QFileInfo fi;
575
    QString realname;
576
    QStringList fuzzyLocales;
577
578
    // see http://www.unicode.org/reports/tr35/#LanguageMatching for inspiration
579
580
    QStringList languages = locale.uiLanguages();
581
#if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
582
    for (int i = languages.size()-1; i >= 0; --i) {
583
        QString lang = languages.at(i);
584
        QString lowerLang = lang.toLower();
585
        if (lang != lowerLang)
586
            languages.insert(i+1, lowerLang);
587
    }
588
#endif
589
590
    // try explicit locales names first
591
    foreach (QString localeName, languages) {
592
        localeName.replace(QLatin1Char('-'), QLatin1Char('_'));
593
594
        realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(".qm") : suffix);
595
        fi.setFile(realname);
596
        if (fi.isReadable() && fi.isFile())
597
            return realname;
598
599
        realname = path + filename + prefix + localeName;
600
        fi.setFile(realname);
601
        if (fi.isReadable() && fi.isFile())
602
            return realname;
603
604
        fuzzyLocales.append(localeName);
605
    }
606
607
    // start guessing
608
    foreach (QString localeName, fuzzyLocales) {
609
        for (;;) {
610
            int rightmost = localeName.lastIndexOf(QLatin1Char('_'));
611
            // no truncations? fail
612
            if (rightmost <= 0)
613
                break;
614
            localeName.truncate(rightmost);
615
616
            realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(".qm") : suffix);
617
            fi.setFile(realname);
618
            if (fi.isReadable() && fi.isFile())
619
                return realname;
620
621
            realname = path + filename + prefix + localeName;
622
            fi.setFile(realname);
623
            if (fi.isReadable() && fi.isFile())
624
                return realname;
625
        }
626
    }
627
628
    if (!suffix.isNull()) {
629
        realname = path + filename + suffix;
630
        fi.setFile(realname);
631
        if (fi.isReadable() && fi.isFile())
632
            return realname;
633
    }
634
635
    realname = path + filename + prefix;
636
    fi.setFile(realname);
637
    if (fi.isReadable() && fi.isFile())
638
        return realname;
639
640
    realname = path + filename;
641
    fi.setFile(realname);
642
    if (fi.isReadable() && fi.isFile())
643
        return realname;
644
645
    return QString();
646
}
647
648
/*!
649
    \since 4.8
650
4f6ccce by David Boddie at 2011-05-11 651
    Loads \a filename + \a prefix + \l{QLocale::uiLanguages()}{ui language
2e90e8d by Denis Dzyubenko at 2011-03-29 652
    name} + \a suffix (".qm" if the \a suffix is not specified), which may be
653
    an absolute file name or relative to \a directory. Returns true if the
654
    translation is successfully loaded; otherwise returns false.
18508ec by Denis Dzyubenko at 2011-02-23 655
656
    The previous contents of this translator object are discarded.
657
658
    If the file name does not exist, other file names are tried
659
    in the following order:
660
661
    \list 1
662
    \o File name without \a suffix appended.
2e90e8d by Denis Dzyubenko at 2011-03-29 663
    \o File name with ui language part after a "_" character stripped and \a suffix.
664
    \o File name with ui language part stripped without \a suffix appended.
665
    \o File name with ui language part stripped further, etc.
18508ec by Denis Dzyubenko at 2011-02-23 666
    \endlist
667
668
    For example, an application running in the locale with the following
4f6ccce by David Boddie at 2011-05-11 669
    \l{QLocale::uiLanguages()}{ui languages} - "es", "fr-CA", "de" might call
18508ec by Denis Dzyubenko at 2011-02-23 670
    load(QLocale::system(), "foo", ".", "/opt/foolib", ".qm"). load() would
2e90e8d by Denis Dzyubenko at 2011-03-29 671
    replace '-' (dash) with '_' (underscore) in the ui language and then try to
672
    open the first existing readable file from this list:
18508ec by Denis Dzyubenko at 2011-02-23 673
674
    \list 1
675
    \o \c /opt/foolib/foo.es.qm
676
    \o \c /opt/foolib/foo.es
677
    \o \c /opt/foolib/foo.fr_CA.qm
678
    \o \c /opt/foolib/foo.fr_CA
679
    \o \c /opt/foolib/foo.de.qm
680
    \o \c /opt/foolib/foo.de
681
    \o \c /opt/foolib/foo.fr.qm
682
    \o \c /opt/foolib/foo.fr
683
    \o \c /opt/foolib/foo.qm
684
    \o \c /opt/foolib/foo.
685
    \o \c /opt/foolib/foo
686
    \endlist
687
4f6ccce by David Boddie at 2011-05-11 688
    On operating systems where file system is case sensitive, QTranslator also
689
    tries to load a lower-cased version of the locale name.
18508ec by Denis Dzyubenko at 2011-02-23 690
*/
691
bool QTranslator::load(const QLocale & locale,
692
                       const QString & filename,
693
                       const QString & prefix,
694
                       const QString & directory,
695
                       const QString & suffix)
696
{
697
    Q_D(QTranslator);
698
    d->clear();
699
    QString fname = find_translation(locale, filename, prefix, directory, suffix);
700
    return !fname.isEmpty() && d->do_load(fname);
701
}
702
e5fcad3 by Lars Knoll at 2009-03-23 703
/*!
704
  \overload load()
705
  \fn bool QTranslator::load(const uchar *data, int len)
706
33604fb by Frederik Schwarzer at 2009-07-06 707
  Loads the QM file data \a data of length \a len into the
e5fcad3 by Lars Knoll at 2009-03-23 708
  translator.
709
710
  The data is not copied. The caller must be able to guarantee that \a data
711
  will not be deleted or modified.
712
*/
713
bool QTranslator::load(const uchar *data, int len)
714
{
715
    Q_D(QTranslator);
716
    d->clear();
717
    return d->do_load(data, len);
718
}
719
720
static quint8 read8(const uchar *data)
721
{
722
    return *data;
723
}
724
725
static quint16 read16(const uchar *data)
726
{
727
    return (data[0] << 8) | (data[1]);
728
}
729
730
static quint32 read32(const uchar *data)
731
{
732
    return (data[0] << 24)
733
        | (data[1] << 16)
734
        | (data[2] << 8)
735
        | (data[3]);
736
}
737
738
bool QTranslatorPrivate::do_load(const uchar *data, int len)
739
{
740
    if (!data || len < MagicLength || memcmp(data, magic, MagicLength))
741
        return false;
742
743
    bool ok = true;
744
    const uchar *end = data + len;
745
746
    data += MagicLength;
747
748
    while (data < end - 4) {
749
        quint8 tag = read8(data++);
750
        quint32 blockLen = read32(data);
751
        data += 4;
752
        if (!tag || !blockLen)
753
            break;
754
        if (data + blockLen > end) {
755
            ok = false;
756
            break;
757
        }
758
759
        if (tag == QTranslatorPrivate::Contexts) {
760
            contextArray = data;
761
            contextLength = blockLen;
762
        } else if (tag == QTranslatorPrivate::Hashes) {
763
            offsetArray = data;
764
            offsetLength = blockLen;
765
        } else if (tag == QTranslatorPrivate::Messages) {
766
            messageArray = data;
767
            messageLength = blockLen;
768
        } else if (tag == QTranslatorPrivate::NumerusRules) {
769
            numerusRulesArray = data;
770
            numerusRulesLength = blockLen;
771
        }
772
773
        data += blockLen;
774
    }
775
776
    return ok;
777
}
778
779
static QString getMessage(const uchar *m, const uchar *end, const char *context,
780
                          const char *sourceText, const char *comment, int numerus)
781
{
782
    const uchar *tn = 0;
783
    uint tn_length = 0;
784
    int currentNumerus = -1;
785
786
    for (;;) {
787
        uchar tag = 0;
788
        if (m < end)
789
            tag = read8(m++);
790
        switch((Tag)tag) {
791
        case Tag_End:
792
            goto end;
793
        case Tag_Translation: {
794
            int len = read32(m);
795
            if (len % 1)
796
                return QString();
797
            m += 4;
798
            if (++currentNumerus == numerus) {
799
                tn_length = len;
800
                tn = m;
801
            }
802
            m += len;
803
            break;
804
        }
805
        case Tag_Obsolete1:
806
            m += 4;
807
            break;
808
        case Tag_SourceText: {
809
            quint32 len = read32(m);
810
            m += 4;
811
            if (!match(m, sourceText, len))
812
                return QString();
813
            m += len;
814
        }
815
            break;
816
        case Tag_Context: {
817
            quint32 len = read32(m);
818
            m += 4;
819
            if (!match(m, context, len))
820
                return QString();
821
            m += len;
822
        }
823
            break;
824
        case Tag_Comment: {
825
            quint32 len = read32(m);
826
            m += 4;
827
            if (*m && !match(m, comment, len))
828
                return QString();
829
            m += len;
830
        }
831
            break;
832
        default:
833
            return QString();
834
        }
835
    }
836
end:
837
    if (!tn)
838
        return QString();
e0fda52 by Oswald Buddenhagen at 2010-02-01 839
    QString str = QString((const QChar *)tn, tn_length/2);
e5fcad3 by Lars Knoll at 2009-03-23 840
    if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
841
        for (int i = 0; i < str.length(); ++i)
842
            str[i] = QChar((str.at(i).unicode() >> 8) + ((str.at(i).unicode() << 8) & 0xff00));
843
    }
844
    return str;
845
}
846
847
QString QTranslatorPrivate::do_translate(const char *context, const char *sourceText,
848
                                         const char *comment, int n) const
849
{
850
    if (context == 0)
851
        context = "";
852
    if (sourceText == 0)
853
        sourceText = "";
854
    if (comment == 0)
855
        comment = "";
856
857
    if (!offsetLength)
858
        return QString();
859
860
    /*
861
        Check if the context belongs to this QTranslator. If many
862
        translators are installed, this step is necessary.
863
    */
864
    if (contextLength) {
865
        quint16 hTableSize = read16(contextArray);
866
        uint g = elfHash(context) % hTableSize;
867
        const uchar *c = contextArray + 2 + (g << 1);
868
        quint16 off = read16(c);
869
        c += 2;
870
        if (off == 0)
871
            return QString();
872
        c = contextArray + (2 + (hTableSize << 1) + (off << 1));
873
874
        for (;;) {
875
            quint8 len = read8(c++);
876
            if (len == 0)
877
                return QString();
878
            if (match(c, context, len))
879
                break;
880
            c += len;
881
        }
882
    }
883
884
    size_t numItems = offsetLength / (2 * sizeof(quint32));
885
    if (!numItems)
886
        return QString();
887
888
    int numerus = 0;
889
    if (n >= 0)
890
        numerus = numerusHelper(n, numerusRulesArray, numerusRulesLength);
891
892
    for (;;) {
7101a3f by Olivier Goffart at 2011-04-01 893
        quint32 h = elfHash(QByteArray(QByteArray(sourceText) + comment).constData());
e5fcad3 by Lars Knoll at 2009-03-23 894
895
        const uchar *start = offsetArray;
896
        const uchar *end = start + ((numItems-1) << 3);
897
        while (start <= end) {
898
            const uchar *middle = start + (((end - start) >> 4) << 3);
899
            uint hash = read32(middle);
900
            if (h == hash) {
901
                start = middle;
902
                break;
903
            } else if (hash < h) {
904
                start = middle + 8;
905
            } else {
906
                end = middle - 8;
907
            }
908
        }
909
910
        if (start <= end) {
911
            // go back on equal key
912
            while (start != offsetArray && read32(start) == read32(start-8))
913
                start -= 8;
914
915
            while (start < offsetArray + offsetLength) {
916
                quint32 rh = read32(start);
917
                start += 4;
918
                if (rh != h)
919
                    break;
920
                quint32 ro = read32(start);
921
                start += 4;
922
                QString tn = getMessage(messageArray + ro, messageArray + messageLength, context,
923
                                        sourceText, comment, numerus);
924
                if (!tn.isNull())
925
                    return tn;
926
            }
927
        }
928
        if (!comment[0])
929
            break;
930
        comment = "";
931
    }
932
    return QString();
933
}
934
935
/*!
936
    Empties this translator of all contents.
937
938
    This function works with stripped translator files.
939
*/
940
941
void QTranslatorPrivate::clear()
942
{
943
    Q_Q(QTranslator);
944
    if (unmapPointer && unmapLength) {
945
#if defined(QT_USE_MMAP)
946
        if (used_mmap)
947
            munmap(unmapPointer, unmapLength);
948
        else
949
#endif
950
            delete [] unmapPointer;
951
    }
952
953
    unmapPointer = 0;
954
    unmapLength = 0;
955
    messageArray = 0;
956
    contextArray = 0;
957
    offsetArray = 0;
958
    numerusRulesArray = 0;
959
    messageLength = 0;
960
    contextLength = 0;
961
    offsetLength = 0;
962
    numerusRulesLength = 0;
963
964
    if (QCoreApplicationPrivate::isTranslatorInstalled(q))
965
        QCoreApplication::postEvent(QCoreApplication::instance(),
966
                                    new QEvent(QEvent::LanguageChange));
967
}
968
969
/*!
970
    Returns the translation for the key (\a context, \a sourceText,
971
    \a disambiguation). If none is found, also tries (\a context, \a
972
    sourceText, ""). If that still fails, returns an empty string.
973
974
    If you need to programatically insert translations in to a
975
    QTranslator, this function can be reimplemented.
976
977
    \sa load()
978
*/
979
QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation) const
980
{
981
    Q_D(const QTranslator);
982
    return d->do_translate(context, sourceText, disambiguation, -1);
983
}
984
985
986
/*!
987
    \overload translate()
988
989
    Returns the translation for the key (\a context, \a sourceText,
990
    \a disambiguation). If none is found, also tries (\a context, \a
991
    sourceText, ""). If that still fails, returns an empty string.
992
993
    If \a n is not -1, it is used to choose an appropriate form for
994
    the translation (e.g. "%n file found" vs. "%n files found").
995
996
    \sa load()
997
*/
998
QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation,
999
                               int n) const
1000
{
1001
    Q_D(const QTranslator);
1002
    // this step is necessary because the 3-parameter translate() overload is virtual
1003
    if (n == -1)
1004
        return translate(context, sourceText, disambiguation);
1005
    return d->do_translate(context, sourceText, disambiguation, n);
1006
}
1007
1008
/*!
1009
    Returns true if this translator is empty, otherwise returns false.
1010
    This function works with stripped and unstripped translation files.
1011
*/
1012
bool QTranslator::isEmpty() const
1013
{
1014
    Q_D(const QTranslator);
1015
    return !d->unmapPointer && !d->unmapLength && !d->messageArray &&
1016
           !d->offsetArray && !d->contextArray;
1017
}
1018
1019
/*!
1020
    \fn QString QTranslator::find(const char *context, const char *sourceText, const char * comment = 0) const
1021
1022
    Use translate(\a context, \a sourceText, \a comment) instead.
1023
*/
1024
1025
QT_END_NAMESPACE
8b2a16b by Markus Goetz at 2009-05-22 1026
1027
#endif // QT_NO_TRANSLATION