~oif-team/ubuntu/natty/qt4-x11/xi2.1

« back to all changes in this revision

Viewing changes to src/corelib/kernel/qtranslator.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-08-24 04:09:09 UTC
  • Revision ID: james.westby@ubuntu.com-20050824040909-xmxe9jfr4a0w5671
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
 
4
**
 
5
** This file is part of the core module of the Qt Toolkit.
 
6
**
 
7
** This file may be distributed under the terms of the Q Public License
 
8
** as defined by Trolltech AS of Norway and appearing in the file
 
9
** LICENSE.QPL included in the packaging of this file.
 
10
**
 
11
** This file may be distributed and/or modified under the terms of the
 
12
** GNU General Public License version 2 as published by the Free Software
 
13
** Foundation and appearing in the file LICENSE.GPL included in the
 
14
** packaging of this file.
 
15
**
 
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
 
17
**   information about Qt Commercial License Agreements.
 
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
 
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
 
20
**
 
21
** Contact info@trolltech.com if any conditions of this licensing are
 
22
** not clear to you.
 
23
**
 
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
26
**
 
27
****************************************************************************/
 
28
 
 
29
#include "qplatformdefs.h"
 
30
 
 
31
#include "qtranslator.h"
 
32
 
 
33
#ifndef QT_NO_TRANSLATION
 
34
 
 
35
#include "qfileinfo.h"
 
36
#include "qstring.h"
 
37
#include "qcoreapplication.h"
 
38
#include "qdatastream.h"
 
39
#include "qfile.h"
 
40
#include "qmap.h"
 
41
#include "qalgorithms.h"
 
42
#include "qhash.h"
 
43
#include "qglobal.h"
 
44
 
 
45
#if defined(Q_OS_UNIX)
 
46
#define QT_USE_MMAP
 
47
#endif
 
48
 
 
49
// most of the headers below are already included in qplatformdefs.h
 
50
// also this lacks Large File support but that's probably irrelevant
 
51
#if defined(QT_USE_MMAP)
 
52
// for mmap
 
53
#include <sys/mman.h>
 
54
#include <errno.h>
 
55
#endif
 
56
 
 
57
#include <stdlib.h>
 
58
 
 
59
#include "qobject_p.h"
 
60
 
 
61
enum Tag { Tag_End = 1, Tag_SourceText16, Tag_Translation, Tag_Context16,
 
62
           Tag_Hash, Tag_SourceText, Tag_Context, Tag_Comment,
 
63
           Tag_Obsolete1 };
 
64
/*
 
65
$ mcookie
 
66
3cb86418caef9c95cd211cbf60a1bddd
 
67
$
 
68
*/
 
69
 
 
70
// magic number for the file
 
71
static const int MagicLength = 16;
 
72
static const uchar magic[MagicLength] = {
 
73
    0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
 
74
    0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
 
75
};
 
76
 
 
77
static bool match(const uchar* found, const char* target, uint len)
 
78
{
 
79
    // 0 means anything, "" means empty
 
80
    return !found || qstrncmp((const char *)found, target, len) == 0 && target[len] == '\0';
 
81
}
 
82
 
 
83
static uint elfHash(const char * name)
 
84
{
 
85
    const uchar *k;
 
86
    uint h = 0;
 
87
    uint g;
 
88
 
 
89
    if (name) {
 
90
        k = (const uchar *) name;
 
91
        while (*k) {
 
92
            h = (h << 4) + *k++;
 
93
            if ((g = (h & 0xf0000000)) != 0)
 
94
                h ^= g >> 24;
 
95
            h &= ~g;
 
96
        }
 
97
    }
 
98
    if (!h)
 
99
        h = 1;
 
100
    return h;
 
101
}
 
102
 
 
103
extern bool qt_detectRTLLanguage();
 
104
 
 
105
class QTranslatorPrivate : public QObjectPrivate
 
106
{
 
107
    Q_DECLARE_PUBLIC(QTranslator)
 
108
public:
 
109
    enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69 };
 
110
 
 
111
    QTranslatorPrivate() : used_mmap(0), unmapPointer(0), unmapLength(0) {}
 
112
 
 
113
    // for mmap'ed files, this is what needs to be unmapped.
 
114
    uint used_mmap : 1;
 
115
    char *unmapPointer;
 
116
    unsigned int unmapLength;
 
117
 
 
118
    // for squeezed but non-file data, this is what needs to be deleted
 
119
    const uchar *messageArray;
 
120
    const uchar *offsetArray;
 
121
    const uchar *contextArray;
 
122
    uint messageLength;
 
123
    uint offsetLength;
 
124
    uint contextLength;
 
125
 
 
126
    bool do_load(const uchar *data, int len);
 
127
 
 
128
    void clear();
 
129
};
 
130
 
 
131
 
 
132
/*!
 
133
    \class QTranslator
 
134
 
 
135
    \brief The QTranslator class provides internationalization support for text
 
136
    output.
 
137
 
 
138
    \ingroup i18n
 
139
    \ingroup environment
 
140
    \mainclass
 
141
 
 
142
    An object of this class contains a set of translations from a
 
143
    source language to a target language. QTranslator provides
 
144
    functions to look up translations in a translation file.
 
145
    Translation files are created using \l{Qt Linguist}.
 
146
 
 
147
    The most common use of QTranslator is to: load a translation
 
148
    file, install it using QApplication::installTranslator(), and use
 
149
    it via QObject::tr(). Here's the \c main() function from the
 
150
    \l{linguist/hellotr}{Hello tr()} example:
 
151
 
 
152
    \quotefromfile linguist/hellotr/main.cpp
 
153
    \skipto main(
 
154
    \printuntil }
 
155
 
 
156
    Note that the translator must be created \e before the
 
157
    application's widgets.
 
158
 
 
159
    Most applications will never need to do anything else with this
 
160
    class. The other functions provided by this class are useful for
 
161
    applications that work on translator files.
 
162
 
 
163
    It is possible to lookup a translation using translate() (as tr()
 
164
    and QApplication::translate() do). The translate() function takes
 
165
    up to three parameters:
 
166
 
 
167
    \list
 
168
    \o The \e context - usually the class name for the tr() caller.
 
169
    \o The \e {source text} - usually the argument to tr().
 
170
    \o The \e comment - an optional comment that helps disambiguate
 
171
       different uses of the same text in the same context.
 
172
    \endlist
 
173
 
 
174
    For example, the "Cancel" in a dialog might have "Anuluj" when the
 
175
    program runs in Polish (in this case the source text would be
 
176
    "Cancel"). The context would (normally) be the dialog's class
 
177
    name; there would normally be no comment, and the translated text
 
178
    would be "Anuluj".
 
179
 
 
180
    But it's not always so simple. The Spanish version of a printer
 
181
    dialog with settings for two-sided printing and binding would
 
182
    probably require both "Activado" and "Activada" as translations
 
183
    for "Enabled". In this case the source text would be "Enabled" in
 
184
    both cases, and the context would be the dialog's class name, but
 
185
    the two items would have disambiguating comments such as
 
186
    "two-sided printing" for one and "binding" for the other. The
 
187
    comment enables the translator to choose the appropriate gender
 
188
    for the Spanish version, and enables Qt to distinguish between
 
189
    translations.
 
190
 
 
191
    \sa QApplication::installTranslator(), QApplication::removeTranslator(),
 
192
        QObject::tr(), QApplication::translate()
 
193
*/
 
194
 
 
195
/*!
 
196
    Constructs an empty message file object with parent \a parent that
 
197
    is not connected to any file.
 
198
*/
 
199
 
 
200
QTranslator::QTranslator(QObject * parent)
 
201
    : QObject(*new QTranslatorPrivate, parent)
 
202
{
 
203
}
 
204
 
 
205
#ifdef QT3_SUPPORT
 
206
/*!
 
207
    \overload
 
208
    \obsolete
 
209
 */
 
210
QTranslator::QTranslator(QObject * parent, const char * name)
 
211
    : QObject(*new QTranslatorPrivate, parent)
 
212
{
 
213
    setObjectName(QString::fromAscii(name));
 
214
}
 
215
#endif
 
216
 
 
217
/*!
 
218
    Destroys the object and frees any allocated resources.
 
219
*/
 
220
 
 
221
QTranslator::~QTranslator()
 
222
{
 
223
    if (QCoreApplication::instance())
 
224
        QCoreApplication::instance()->removeTranslator(this);
 
225
    Q_D(QTranslator);
 
226
    d->clear();
 
227
}
 
228
 
 
229
/*!
 
230
    Loads \a filename + \a suffix (".qm" if the \a suffix is
 
231
    not specified), which may be an absolute file name or relative
 
232
    to \a directory. The previous contents of this translator object
 
233
    is discarded.
 
234
 
 
235
    If the file name does not exist, other file names are tried
 
236
    in the following order:
 
237
 
 
238
    \list 1
 
239
    \o File name without \a suffix appended.
 
240
    \o File name with text after a character in \a search_delimiters
 
241
       stripped ("_." is the default for \a search_delimiters if it is
 
242
       an empty string) and \a suffix.
 
243
    \o File name stripped without \a suffix appended.
 
244
    \o File name stripped further, etc.
 
245
    \endlist
 
246
 
 
247
    For example, an application running in the fr_CA locale
 
248
    (French-speaking Canada) might call load("foo.fr_ca",
 
249
    "/opt/foolib"). load() would then try to open the first existing
 
250
    readable file from this list:
 
251
 
 
252
    \list 1
 
253
    \o \c /opt/foolib/foo.fr_ca.qm
 
254
    \o \c /opt/foolib/foo.fr_ca
 
255
    \o \c /opt/foolib/foo.fr.qm
 
256
    \o \c /opt/foolib/foo.fr
 
257
    \o \c /opt/foolib/foo.qm
 
258
    \o \c /opt/foolib/foo
 
259
    \endlist
 
260
*/
 
261
 
 
262
bool QTranslator::load(const QString & filename, const QString & directory,
 
263
                       const QString & search_delimiters,
 
264
                       const QString & suffix)
 
265
{
 
266
    Q_D(QTranslator);
 
267
    d->clear();
 
268
 
 
269
    QString prefix;
 
270
 
 
271
    if (filename[0] == QLatin1Char('/')
 
272
#ifdef Q_WS_WIN
 
273
         || (filename[0].isLetter() && filename[1] == QLatin1Char(':')) || filename[0] == QLatin1Char('\\')
 
274
#endif
 
275
        )
 
276
        prefix = QLatin1String("");
 
277
    else
 
278
        prefix = directory;
 
279
 
 
280
    if (prefix.length()) {
 
281
        if (prefix[int(prefix.length()-1)] != QLatin1Char('/'))
 
282
            prefix += QLatin1Char('/');
 
283
    }
 
284
 
 
285
    QString fname = filename;
 
286
    QString realname;
 
287
    QString delims;
 
288
    delims = search_delimiters.isNull() ? QString::fromLatin1("_.") : search_delimiters;
 
289
 
 
290
    for (;;) {
 
291
        QFileInfo fi;
 
292
 
 
293
        realname = prefix + fname + (suffix.isNull() ? QString::fromLatin1(".qm") : suffix);
 
294
        fi.setFile(realname);
 
295
        if (fi.isReadable())
 
296
            break;
 
297
 
 
298
        realname = prefix + fname;
 
299
        fi.setFile(realname);
 
300
        if (fi.isReadable())
 
301
            break;
 
302
 
 
303
        int rightmost = 0;
 
304
        for (int i = 0; i < (int)delims.length(); i++) {
 
305
            int k = fname.lastIndexOf(delims[i]);
 
306
            if (k > rightmost)
 
307
                rightmost = k;
 
308
        }
 
309
 
 
310
        // no truncations? fail
 
311
        if (rightmost == 0)
 
312
            return false;
 
313
 
 
314
        fname.truncate(rightmost);
 
315
    }
 
316
 
 
317
    // realname is now the fully qualified name of a readable file.
 
318
 
 
319
    bool ok = false;
 
320
 
 
321
#ifdef QT_USE_MMAP
 
322
 
 
323
#ifndef MAP_FILE
 
324
#define MAP_FILE 0
 
325
#endif
 
326
#ifndef MAP_FAILED
 
327
#define MAP_FAILED -1
 
328
#endif
 
329
 
 
330
    int fd = -1;
 
331
    if (!realname.startsWith(QLatin1String(":")))
 
332
        fd = QT_OPEN(QFile::encodeName(realname), O_RDONLY,
 
333
#if defined(Q_OS_WIN)
 
334
                 _S_IREAD | _S_IWRITE
 
335
#else
 
336
                 0666
 
337
#endif
 
338
                );
 
339
    if (fd >= 0) {
 
340
        struct stat st;
 
341
        if (!fstat(fd, &st)) {
 
342
            char *ptr;
 
343
            ptr = reinterpret_cast<char *>(
 
344
                        mmap(0, st.st_size,             // any address, whole file
 
345
                             PROT_READ,                 // read-only memory
 
346
                             MAP_FILE | MAP_PRIVATE,    // swap-backed map from file
 
347
                             fd, 0));                   // from offset 0 of fd
 
348
            if (ptr && ptr != reinterpret_cast<char *>(MAP_FAILED)) {
 
349
                d->used_mmap = true;
 
350
                d->unmapPointer = ptr;
 
351
                d->unmapLength = st.st_size;
 
352
                ok = true;
 
353
            }
 
354
        }
 
355
        ::close(fd);
 
356
    }
 
357
#endif // QT_USE_MMAP
 
358
 
 
359
    if (!ok) {
 
360
        QFile file(realname);
 
361
        if (!file.exists())
 
362
            return false;
 
363
        d->unmapLength = file.size();
 
364
        d->unmapPointer = new char[d->unmapLength];
 
365
 
 
366
        if (file.open(QIODevice::ReadOnly))
 
367
            ok = (d->unmapLength == (uint)file.read(d->unmapPointer, d->unmapLength));
 
368
 
 
369
        if (!ok) {
 
370
            delete [] d->unmapPointer;
 
371
            d->unmapPointer = 0;
 
372
            d->unmapLength = 0;
 
373
            return false;
 
374
        }
 
375
    }
 
376
 
 
377
    return d->do_load(reinterpret_cast<const uchar *>(d->unmapPointer), d->unmapLength);
 
378
}
 
379
 
 
380
/*!
 
381
  \overload
 
382
  \fn bool QTranslator::load(const uchar *data, int len)
 
383
 
 
384
  Loads the .qm file data \a data of length \a len into the
 
385
  translator.
 
386
 
 
387
  The data is not copied. The caller must be able to guarantee that \a data
 
388
  will not be deleted or modified.
 
389
*/
 
390
bool QTranslator::load(const uchar *data, int len)
 
391
{
 
392
    Q_D(QTranslator);
 
393
    d->clear();
 
394
    return d->do_load(data, len);
 
395
}
 
396
 
 
397
static quint8 read8(const uchar *data)
 
398
{
 
399
    return *data;
 
400
}
 
401
 
 
402
static quint16 read16(const uchar *data)
 
403
{
 
404
    return (data[0] << 8) | (data[1]);
 
405
}
 
406
 
 
407
static quint32 read32(const uchar *data)
 
408
{
 
409
    return (data[0] << 24)
 
410
        | (data[1] << 16)
 
411
        | (data[2] << 8)
 
412
        | (data[3]);
 
413
}
 
414
 
 
415
bool QTranslatorPrivate::do_load(const uchar *data, int len)
 
416
{
 
417
    if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
 
418
        clear();
 
419
        return false;
 
420
    }
 
421
 
 
422
    bool ok = true;
 
423
    const uchar *end = data + len;
 
424
 
 
425
    data += MagicLength;
 
426
 
 
427
    while (data < end - 4) {
 
428
        quint8 tag = read8(data++);
 
429
        quint32 blockLen = read32(data);
 
430
        data += 4;
 
431
        if (!tag || !blockLen)
 
432
            break;
 
433
        if (data + blockLen > end) {
 
434
            ok = false;
 
435
            break;
 
436
        }
 
437
 
 
438
        if (tag == QTranslatorPrivate::Contexts) {
 
439
            contextArray = data;
 
440
            contextLength = blockLen;
 
441
        } else if (tag == QTranslatorPrivate::Hashes) {
 
442
            offsetArray = data;
 
443
            offsetLength = blockLen;
 
444
        } else if (tag == QTranslatorPrivate::Messages) {
 
445
            messageArray = data;
 
446
            messageLength = blockLen;
 
447
        }
 
448
 
 
449
        data += blockLen;
 
450
    }
 
451
 
 
452
    return ok;
 
453
}
 
454
 
 
455
/*!
 
456
    Empties this translator of all contents.
 
457
 
 
458
    This function works with stripped translator files.
 
459
*/
 
460
 
 
461
void QTranslatorPrivate::clear()
 
462
{
 
463
    if (unmapPointer && unmapLength) {
 
464
#if defined(QT_USE_MMAP)
 
465
        if(used_mmap)
 
466
            munmap(unmapPointer, unmapLength);
 
467
        else
 
468
#else
 
469
            delete [] unmapPointer;
 
470
#endif
 
471
        unmapPointer = 0;
 
472
        unmapLength = 0;
 
473
    }
 
474
 
 
475
    messageArray = 0;
 
476
    contextArray = 0;
 
477
    offsetArray = 0;
 
478
    messageLength = 0;
 
479
    contextLength = 0;
 
480
    offsetLength = 0;
 
481
 
 
482
    QEvent ev(QEvent::LanguageChange);
 
483
    QCoreApplication::sendEvent(QCoreApplication::instance(), &ev);
 
484
}
 
485
 
 
486
 
 
487
static QString getMessage(const uchar *m, const uchar *end, const char *context, const char *sourceText, const char *comment)
 
488
{
 
489
    const uchar *tn = 0;
 
490
    uint tn_length = 0;
 
491
 
 
492
    for (;;) {
 
493
        uchar tag = 0;
 
494
        if (m < end)
 
495
            tag = read8(m++);
 
496
        switch((Tag)tag) {
 
497
        case Tag_End:
 
498
            goto end;
 
499
        case Tag_Translation:
 
500
            tn_length = read32(m);
 
501
            if (tn_length % 1)
 
502
                return QString();
 
503
            m += 4;
 
504
            if (tn_length == 0xffffffff)
 
505
                return QString();
 
506
            tn = m;
 
507
            m += tn_length;
 
508
            break;
 
509
        case Tag_Hash:
 
510
            m += 4;
 
511
            break;
 
512
        case Tag_SourceText: {
 
513
            quint32 len = read32(m);
 
514
            m += 4;
 
515
            if (!match(m, sourceText, len))
 
516
                return QString();
 
517
            m += len;
 
518
        }
 
519
            break;
 
520
        case Tag_Context: {
 
521
            quint32 len = read32(m);
 
522
            m += 4;
 
523
            if (*m && !match(m, context, len))
 
524
                return QString();
 
525
            m += len;
 
526
        }
 
527
            break;
 
528
        case Tag_Comment: {
 
529
            quint32 len = read32(m);
 
530
            m += 4;
 
531
            if (*m && !match(m, comment, len))
 
532
                return QString();
 
533
            m += len;
 
534
        }
 
535
            break;
 
536
        default:
 
537
            return QString();
 
538
        }
 
539
    }
 
540
end:
 
541
    if (!tn)
 
542
        return QString();
 
543
    QString str = QString::fromUtf16((const ushort *)tn, tn_length/2);
 
544
    if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
 
545
        for (int i = 0; i < str.length(); ++i)
 
546
            str[i] = QChar((str.at(i).unicode() >> 8) + ((str.at(i).unicode() << 8) & 0xff00));
 
547
    }
 
548
    return str;
 
549
}
 
550
 
 
551
 
 
552
/*!
 
553
    Returns the translation for the key (\a context, \a sourceText,
 
554
    \a comment). If none is found, also tries (\a context, \a
 
555
    sourceText, ""). If that still fails, returns an empty string.
 
556
 
 
557
    \sa load()
 
558
*/
 
559
QString QTranslator::translate(const char *context, const char *sourceText, const char *comment) const
 
560
{
 
561
    Q_D(const QTranslator);
 
562
    if (context == 0)
 
563
        context = "";
 
564
    if (sourceText == 0)
 
565
        sourceText = "";
 
566
    if (comment == 0)
 
567
        comment = "";
 
568
 
 
569
    if (!d->offsetLength)
 
570
        return QString();
 
571
 
 
572
    /*
 
573
        Check if the context belongs to this QTranslator. If many
 
574
        translators are installed, this step is necessary.
 
575
    */
 
576
    if (d->contextLength) {
 
577
        quint16 hTableSize = read16(d->contextArray);
 
578
        uint g = elfHash(context) % hTableSize;
 
579
        const uchar *c = d->contextArray + 2 + (g << 1);
 
580
        quint16 off = read16(c);
 
581
        c += 2;
 
582
        if (off == 0)
 
583
            return QString();
 
584
        c = d->contextArray + (2 + (hTableSize << 1) + (off << 1));
 
585
 
 
586
        for (;;) {
 
587
            quint8 len = read8(c++);
 
588
            if (len == 0)
 
589
                return QString();
 
590
            if (match(c, context, len))
 
591
                break;
 
592
            c += len;
 
593
        }
 
594
    }
 
595
 
 
596
    size_t numItems = d->offsetLength / (2 * sizeof(quint32));
 
597
    if (!numItems)
 
598
        return QString();
 
599
 
 
600
    for (;;) {
 
601
        quint32 h = elfHash(QByteArray(sourceText) + comment);
 
602
 
 
603
        const uchar *start = d->offsetArray;
 
604
        const uchar *end = start + ((numItems-1) << 3);
 
605
        while (start <= end) {
 
606
            const uchar *middle = start + (((end - start) >> 4) << 3);
 
607
            uint hash = read32(middle);
 
608
            if (h == hash) {
 
609
                start = middle;
 
610
                break;
 
611
            } else if (hash < h) {
 
612
                start = middle + 8;
 
613
            } else {
 
614
                end = middle - 8;
 
615
            }
 
616
        }
 
617
 
 
618
        if (start <= end) {
 
619
            // go back on equal key
 
620
            while (start != d->offsetArray && read32(start) == read32(start-8))
 
621
                start -= 8;
 
622
 
 
623
            while (start < d->offsetArray + d->offsetLength) {
 
624
                quint32 rh = read32(start);
 
625
                start += 4;
 
626
                if (rh != h)
 
627
                    break;
 
628
                quint32 ro = read32(start);
 
629
                start += 4;
 
630
                QString tn = getMessage(d->messageArray + ro, d->messageArray + d->messageLength, context, sourceText, comment);
 
631
                if (!tn.isNull())
 
632
                    return tn;
 
633
            }
 
634
        }
 
635
        if (!comment[0])
 
636
            break;
 
637
        comment = "";
 
638
    }
 
639
    return QString();
 
640
}
 
641
 
 
642
/*!
 
643
    Returns true if this translator is empty, otherwise returns false.
 
644
    This function works with stripped and unstripped translation files.
 
645
*/
 
646
bool QTranslator::isEmpty() const
 
647
{
 
648
    Q_D(const QTranslator);
 
649
    return !d->unmapPointer && !d->unmapLength && !d->messageArray &&
 
650
           !d->offsetArray && !d->contextArray;
 
651
}
 
652
 
 
653
 
 
654
/*!
 
655
    \fn QString QTranslator::find(const char *context, const char *sourceText, const char * comment = 0) const
 
656
 
 
657
    Use translate(\a context, \a sourceText, \a comment) instead.
 
658
*/
 
659
 
 
660
#endif // QT_NO_TRANSLATION