util/tools/linguist/shared/qm.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Linguist of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "translator.h"
       
    43 
       
    44 #ifndef QT_BOOTSTRAPPED
       
    45 #include <QtCore/QCoreApplication>
       
    46 #endif
       
    47 #include <QtCore/QDebug>
       
    48 #include <QtCore/QDir>
       
    49 #include <QtCore/QFile>
       
    50 #include <QtCore/QFileInfo>
       
    51 #include <QtCore/QMap>
       
    52 #include <QtCore/QString>
       
    53 #include <QtCore/QTextCodec>
       
    54 
       
    55 QT_BEGIN_NAMESPACE
       
    56 
       
    57 // magic number for the file
       
    58 static const int MagicLength = 16;
       
    59 static const uchar magic[MagicLength] = {
       
    60     0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
       
    61     0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
       
    62 };
       
    63 
       
    64 
       
    65 namespace {
       
    66 
       
    67 enum Tag {
       
    68     Tag_End          = 1,
       
    69     Tag_SourceText16 = 2,
       
    70     Tag_Translation  = 3,
       
    71     Tag_Context16    = 4,
       
    72     Tag_Obsolete1    = 5,
       
    73     Tag_SourceText   = 6,
       
    74     Tag_Context      = 7,
       
    75     Tag_Comment      = 8,
       
    76     Tag_Obsolete2    = 9
       
    77 };
       
    78 
       
    79 enum Prefix {
       
    80     NoPrefix,
       
    81     Hash,
       
    82     HashContext,
       
    83     HashContextSourceText,
       
    84     HashContextSourceTextComment
       
    85 };
       
    86 
       
    87 } // namespace anon
       
    88 
       
    89 static uint elfHash(const QByteArray &ba)
       
    90 {
       
    91     const uchar *k = (const uchar *)ba.data();
       
    92     uint h = 0;
       
    93     uint g;
       
    94 
       
    95     if (k) {
       
    96         while (*k) {
       
    97             h = (h << 4) + *k++;
       
    98             if ((g = (h & 0xf0000000)) != 0)
       
    99                 h ^= g >> 24;
       
   100             h &= ~g;
       
   101         }
       
   102     }
       
   103     if (!h)
       
   104         h = 1;
       
   105     return h;
       
   106 }
       
   107 
       
   108 class ByteTranslatorMessage
       
   109 {
       
   110 public:
       
   111     ByteTranslatorMessage(
       
   112             const QByteArray &context,
       
   113             const QByteArray &sourceText,
       
   114             const QByteArray &comment,
       
   115             const QStringList &translations) :
       
   116         m_context(context),
       
   117         m_sourcetext(sourceText),
       
   118         m_comment(comment),
       
   119         m_translations(translations)
       
   120     {}
       
   121     const QByteArray &context() const { return m_context; }
       
   122     const QByteArray &sourceText() const { return m_sourcetext; }
       
   123     const QByteArray &comment() const { return m_comment; }
       
   124     const QStringList &translations() const { return m_translations; }
       
   125     bool operator<(const ByteTranslatorMessage& m) const;
       
   126 
       
   127 private:
       
   128     QByteArray m_context;
       
   129     QByteArray m_sourcetext;
       
   130     QByteArray m_comment;
       
   131     QStringList m_translations;
       
   132 };
       
   133 
       
   134 Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_MOVABLE_TYPE);
       
   135 
       
   136 bool ByteTranslatorMessage::operator<(const ByteTranslatorMessage& m) const
       
   137 {
       
   138     if (m_context != m.m_context)
       
   139         return m_context < m.m_context;
       
   140     if (m_sourcetext != m.m_sourcetext)
       
   141         return m_sourcetext < m.m_sourcetext;
       
   142     return m_comment < m.m_comment;
       
   143 }
       
   144 
       
   145 class Releaser
       
   146 {
       
   147 public:
       
   148     struct Offset {
       
   149         Offset()
       
   150             : h(0), o(0)
       
   151         {}
       
   152         Offset(uint hash, uint offset)
       
   153             : h(hash), o(offset)
       
   154         {}
       
   155 
       
   156         bool operator<(const Offset &other) const {
       
   157             return (h != other.h) ? h < other.h : o < other.o;
       
   158         }
       
   159         bool operator==(const Offset &other) const {
       
   160             return h == other.h && o == other.o;
       
   161         }
       
   162         uint h;
       
   163         uint o;
       
   164     };
       
   165 
       
   166     enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
       
   167 
       
   168     Releaser() : m_codec(0) {}
       
   169 
       
   170     void setCodecName(const QByteArray &codecName)
       
   171     {
       
   172         m_codec = QTextCodec::codecForName(codecName);
       
   173     }
       
   174 
       
   175     bool save(QIODevice *iod);
       
   176 
       
   177     void insert(const TranslatorMessage &msg, const QStringList &tlns, bool forceComment);
       
   178     void insertIdBased(const TranslatorMessage &message, const QStringList &tlns);
       
   179 
       
   180     void squeeze(TranslatorSaveMode mode);
       
   181 
       
   182     void setNumerusRules(const QByteArray &rules);
       
   183 
       
   184 private:
       
   185     Q_DISABLE_COPY(Releaser)
       
   186 
       
   187     // This should reproduce the byte array fetched from the source file, which
       
   188     // on turn should be the same as passed to the actual tr(...) calls
       
   189     QByteArray originalBytes(const QString &str, bool isUtf8) const;
       
   190 
       
   191     void insertInternal(const TranslatorMessage &message, const QStringList &tlns,
       
   192                         bool forceComment, bool isUtf8);
       
   193 
       
   194     static Prefix commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2);
       
   195 
       
   196     static uint msgHash(const ByteTranslatorMessage &msg);
       
   197 
       
   198     void writeMessage(const ByteTranslatorMessage & msg, QDataStream & stream,
       
   199         TranslatorSaveMode strip, Prefix prefix) const;
       
   200 
       
   201     // for squeezed but non-file data, this is what needs to be deleted
       
   202     QByteArray m_messageArray;
       
   203     QByteArray m_offsetArray;
       
   204     QByteArray m_contextArray;
       
   205     QMap<ByteTranslatorMessage, void *> m_messages;
       
   206     QByteArray m_numerusRules;
       
   207 
       
   208     // Used to reproduce the original bytes
       
   209     QTextCodec *m_codec;
       
   210 };
       
   211 
       
   212 QByteArray Releaser::originalBytes(const QString &str, bool isUtf8) const
       
   213 {
       
   214     if (str.isEmpty()) {
       
   215         // Do not use QByteArray() here as the result of the serialization
       
   216         // will be different.
       
   217         return QByteArray("");
       
   218     }
       
   219     if (isUtf8)
       
   220         return str.toUtf8();
       
   221     return m_codec ? m_codec->fromUnicode(str) : str.toLatin1();
       
   222 }
       
   223 
       
   224 uint Releaser::msgHash(const ByteTranslatorMessage &msg)
       
   225 {
       
   226     return elfHash(msg.sourceText() + msg.comment());
       
   227 }
       
   228 
       
   229 Prefix Releaser::commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2)
       
   230 {
       
   231     if (msgHash(m1) != msgHash(m2))
       
   232         return NoPrefix;
       
   233     if (m1.context() != m2.context())
       
   234         return Hash;
       
   235     if (m1.sourceText() != m2.sourceText())
       
   236         return HashContext;
       
   237     if (m1.comment() != m2.comment())
       
   238         return HashContextSourceText;
       
   239     return HashContextSourceTextComment;
       
   240 }
       
   241 
       
   242 void Releaser::writeMessage(const ByteTranslatorMessage &msg, QDataStream &stream,
       
   243     TranslatorSaveMode mode, Prefix prefix) const
       
   244 {
       
   245     for (int i = 0; i < msg.translations().count(); ++i)
       
   246         stream << quint8(Tag_Translation) << msg.translations().at(i);
       
   247 
       
   248     if (mode == SaveEverything)
       
   249         prefix = HashContextSourceTextComment;
       
   250 
       
   251     // lrelease produces "wrong" QM files for QByteArrays that are .isNull().
       
   252     switch (prefix) {
       
   253     default:
       
   254     case HashContextSourceTextComment:
       
   255         stream << quint8(Tag_Comment) << msg.comment();
       
   256         // fall through
       
   257     case HashContextSourceText:
       
   258         stream << quint8(Tag_SourceText) << msg.sourceText();
       
   259         // fall through
       
   260     case HashContext:
       
   261         stream << quint8(Tag_Context) << msg.context();
       
   262         break;
       
   263     }
       
   264 
       
   265     stream << quint8(Tag_End);
       
   266 }
       
   267 
       
   268 
       
   269 bool Releaser::save(QIODevice *iod)
       
   270 {
       
   271     QDataStream s(iod);
       
   272     s.writeRawData((const char *)magic, MagicLength);
       
   273 
       
   274     if (!m_offsetArray.isEmpty()) {
       
   275         quint32 oas = quint32(m_offsetArray.size());
       
   276         s << quint8(Hashes) << oas;
       
   277         s.writeRawData(m_offsetArray.constData(), oas);
       
   278     }
       
   279     if (!m_messageArray.isEmpty()) {
       
   280         quint32 mas = quint32(m_messageArray.size());
       
   281         s << quint8(Messages) << mas;
       
   282         s.writeRawData(m_messageArray.constData(), mas);
       
   283     }
       
   284     if (!m_contextArray.isEmpty()) {
       
   285         quint32 cas = quint32(m_contextArray.size());
       
   286         s << quint8(Contexts) << cas;
       
   287         s.writeRawData(m_contextArray.constData(), cas);
       
   288     }
       
   289     if (!m_numerusRules.isEmpty()) {
       
   290         quint32 nrs = m_numerusRules.size();
       
   291         s << quint8(NumerusRules) << nrs;
       
   292         s.writeRawData(m_numerusRules.constData(), nrs);
       
   293     }
       
   294     return true;
       
   295 }
       
   296 
       
   297 void Releaser::squeeze(TranslatorSaveMode mode)
       
   298 {
       
   299     if (m_messages.isEmpty() && mode == SaveEverything)
       
   300         return;
       
   301 
       
   302     QMap<ByteTranslatorMessage, void *> messages = m_messages;
       
   303 
       
   304     // re-build contents
       
   305     m_messageArray.clear();
       
   306     m_offsetArray.clear();
       
   307     m_contextArray.clear();
       
   308     m_messages.clear();
       
   309 
       
   310     QMap<Offset, void *> offsets;
       
   311 
       
   312     QDataStream ms(&m_messageArray, QIODevice::WriteOnly);
       
   313     QMap<ByteTranslatorMessage, void *>::const_iterator it, next;
       
   314     int cpPrev = 0, cpNext = 0;
       
   315     for (it = messages.constBegin(); it != messages.constEnd(); ++it) {
       
   316         cpPrev = cpNext;
       
   317         next = it;
       
   318         ++next;
       
   319         if (next == messages.constEnd())
       
   320             cpNext = 0;
       
   321         else
       
   322             cpNext = commonPrefix(it.key(), next.key());
       
   323         offsets.insert(Offset(msgHash(it.key()), ms.device()->pos()), (void *)0);
       
   324         writeMessage(it.key(), ms, mode, Prefix(qMax(cpPrev, cpNext + 1)));
       
   325     }
       
   326 
       
   327     QMap<Offset, void *>::Iterator offset;
       
   328     offset = offsets.begin();
       
   329     QDataStream ds(&m_offsetArray, QIODevice::WriteOnly);
       
   330     while (offset != offsets.end()) {
       
   331         Offset k = offset.key();
       
   332         ++offset;
       
   333         ds << quint32(k.h) << quint32(k.o);
       
   334     }
       
   335 
       
   336     if (mode == SaveStripped) {
       
   337         QMap<QByteArray, int> contextSet;
       
   338         for (it = messages.constBegin(); it != messages.constEnd(); ++it)
       
   339             ++contextSet[it.key().context()];
       
   340 
       
   341         quint16 hTableSize;
       
   342         if (contextSet.size() < 200)
       
   343             hTableSize = (contextSet.size() < 60) ? 151 : 503;
       
   344         else if (contextSet.size() < 2500)
       
   345             hTableSize = (contextSet.size() < 750) ? 1511 : 5003;
       
   346         else
       
   347             hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2;
       
   348 
       
   349         QMultiMap<int, QByteArray> hashMap;
       
   350         QMap<QByteArray, int>::const_iterator c;
       
   351         for (c = contextSet.constBegin(); c != contextSet.constEnd(); ++c)
       
   352             hashMap.insert(elfHash(c.key()) % hTableSize, c.key());
       
   353 
       
   354         /*
       
   355           The contexts found in this translator are stored in a hash
       
   356           table to provide fast lookup. The context array has the
       
   357           following format:
       
   358 
       
   359               quint16 hTableSize;
       
   360               quint16 hTable[hTableSize];
       
   361               quint8  contextPool[...];
       
   362 
       
   363           The context pool stores the contexts as Pascal strings:
       
   364 
       
   365               quint8  len;
       
   366               quint8  data[len];
       
   367 
       
   368           Let's consider the look-up of context "FunnyDialog".  A
       
   369           hash value between 0 and hTableSize - 1 is computed, say h.
       
   370           If hTable[h] is 0, "FunnyDialog" is not covered by this
       
   371           translator. Else, we check in the contextPool at offset
       
   372           2 * hTable[h] to see if "FunnyDialog" is one of the
       
   373           contexts stored there, until we find it or we meet the
       
   374           empty string.
       
   375         */
       
   376         m_contextArray.resize(2 + (hTableSize << 1));
       
   377         QDataStream t(&m_contextArray, QIODevice::WriteOnly);
       
   378 
       
   379         quint16 *hTable = new quint16[hTableSize];
       
   380         memset(hTable, 0, hTableSize * sizeof(quint16));
       
   381 
       
   382         t << hTableSize;
       
   383         t.device()->seek(2 + (hTableSize << 1));
       
   384         t << quint16(0); // the entry at offset 0 cannot be used
       
   385         uint upto = 2;
       
   386 
       
   387         QMap<int, QByteArray>::const_iterator entry = hashMap.constBegin();
       
   388         while (entry != hashMap.constEnd()) {
       
   389             int i = entry.key();
       
   390             hTable[i] = quint16(upto >> 1);
       
   391 
       
   392             do {
       
   393                 const char *con = entry.value().constData();
       
   394                 uint len = uint(entry.value().length());
       
   395                 len = qMin(len, 255u);
       
   396                 t << quint8(len);
       
   397                 t.writeRawData(con, len);
       
   398                 upto += 1 + len;
       
   399                 ++entry;
       
   400             } while (entry != hashMap.constEnd() && entry.key() == i);
       
   401             if (upto & 0x1) {
       
   402                 // offsets have to be even
       
   403                 t << quint8(0); // empty string
       
   404                 ++upto;
       
   405             }
       
   406         }
       
   407         t.device()->seek(2);
       
   408         for (int j = 0; j < hTableSize; j++)
       
   409             t << hTable[j];
       
   410         delete [] hTable;
       
   411 
       
   412         if (upto > 131072) {
       
   413             qWarning("Releaser::squeeze: Too many contexts");
       
   414             m_contextArray.clear();
       
   415         }
       
   416     }
       
   417 }
       
   418 
       
   419 void Releaser::insertInternal(const TranslatorMessage &message, const QStringList &tlns,
       
   420                               bool forceComment, bool isUtf8)
       
   421 {
       
   422     ByteTranslatorMessage bmsg(originalBytes(message.context(), isUtf8),
       
   423                                originalBytes(message.sourceText(), isUtf8),
       
   424                                originalBytes(message.comment(), isUtf8),
       
   425                                tlns);
       
   426     if (!forceComment) {
       
   427         ByteTranslatorMessage bmsg2(
       
   428                 bmsg.context(), bmsg.sourceText(), QByteArray(""), bmsg.translations());
       
   429         if (!m_messages.contains(bmsg2)) {
       
   430             m_messages.insert(bmsg2, 0);
       
   431             return;
       
   432         }
       
   433     }
       
   434     m_messages.insert(bmsg, 0);
       
   435 }
       
   436 
       
   437 void Releaser::insert(const TranslatorMessage &message, const QStringList &tlns, bool forceComment)
       
   438 {
       
   439     insertInternal(message, tlns, forceComment, message.isUtf8());
       
   440     if (message.isUtf8() && message.isNonUtf8())
       
   441         insertInternal(message, tlns, forceComment, false);
       
   442 }
       
   443 
       
   444 void Releaser::insertIdBased(const TranslatorMessage &message, const QStringList &tlns)
       
   445 {
       
   446     ByteTranslatorMessage bmsg("", originalBytes(message.id(), false), "", tlns);
       
   447     m_messages.insert(bmsg, 0);
       
   448 }
       
   449 
       
   450 void Releaser::setNumerusRules(const QByteArray &rules)
       
   451 {
       
   452     m_numerusRules = rules;
       
   453 }
       
   454 
       
   455 static quint8 read8(const uchar *data)
       
   456 {
       
   457     return *data;
       
   458 }
       
   459 
       
   460 static quint32 read32(const uchar *data)
       
   461 {
       
   462     return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
       
   463 }
       
   464 
       
   465 static void fromBytes(const char *str, int len, QTextCodec *codec, QTextCodec *utf8Codec,
       
   466                       QString *out, QString *utf8Out,
       
   467                       bool *isSystem, bool *isUtf8, bool *needs8Bit)
       
   468 {
       
   469     for (int i = 0; i < len; ++i)
       
   470         if (str[i] & 0x80) {
       
   471             if (utf8Codec) {
       
   472                 QTextCodec::ConverterState cvtState;
       
   473                 *utf8Out = utf8Codec->toUnicode(str, len, &cvtState);
       
   474                 *isUtf8 = !cvtState.invalidChars;
       
   475             }
       
   476             QTextCodec::ConverterState cvtState;
       
   477             *out = codec->toUnicode(str, len, &cvtState);
       
   478             *isSystem = !cvtState.invalidChars;
       
   479             *needs8Bit = true;
       
   480             return;
       
   481         }
       
   482     *out = QString::fromLatin1(str, len);
       
   483     *isSystem = true;
       
   484     if (utf8Codec) {
       
   485         *utf8Out = *out;
       
   486         *isUtf8 = true;
       
   487     }
       
   488     *needs8Bit = false;
       
   489 }
       
   490 
       
   491 bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd)
       
   492 {
       
   493     QByteArray ba = dev.readAll();
       
   494     const uchar *data = (uchar*)ba.data();
       
   495     int len = ba.size();
       
   496     if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
       
   497         cd.appendError(QLatin1String("QM-Format error: magic marker missing"));
       
   498         return false;
       
   499     }
       
   500 
       
   501     enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
       
   502 
       
   503     // for squeezed but non-file data, this is what needs to be deleted
       
   504     const uchar *messageArray = 0;
       
   505     const uchar *offsetArray = 0;
       
   506     const uchar *contextArray = 0;
       
   507     const uchar *numerusRulesArray = 0;
       
   508     uint messageLength = 0;
       
   509     uint offsetLength = 0;
       
   510     uint contextLength = 0;
       
   511     uint numerusRulesLength = 0;
       
   512 
       
   513     bool ok = true;
       
   514     const uchar *end = data + len;
       
   515 
       
   516     data += MagicLength;
       
   517 
       
   518     while (data < end - 4) {
       
   519         quint8 tag = read8(data++);
       
   520         quint32 blockLen = read32(data);
       
   521         //qDebug() << "TAG:" << tag <<  "BLOCKLEN:" << blockLen;
       
   522         data += 4;
       
   523         if (!tag || !blockLen)
       
   524             break;
       
   525         if (data + blockLen > end) {
       
   526             ok = false;
       
   527             break;
       
   528         }
       
   529 
       
   530         if (tag == Contexts) {
       
   531             contextArray = data;
       
   532             contextLength = blockLen;
       
   533             //qDebug() << "CONTEXTS: " << contextLength << QByteArray((const char *)contextArray, contextLength).toHex();
       
   534         } else if (tag == Hashes) {
       
   535             offsetArray = data;
       
   536             offsetLength = blockLen;
       
   537             //qDebug() << "HASHES: " << offsetLength << QByteArray((const char *)offsetArray, offsetLength).toHex();
       
   538         } else if (tag == Messages) {
       
   539             messageArray = data;
       
   540             messageLength = blockLen;
       
   541             //qDebug() << "MESSAGES: " << messageLength << QByteArray((const char *)messageArray, messageLength).toHex();
       
   542         } else if (tag == NumerusRules) {
       
   543             numerusRulesArray = data;
       
   544             numerusRulesLength = blockLen;
       
   545             //qDebug() << "NUMERUSRULES: " << numerusRulesLength << QByteArray((const char *)numerusRulesArray, numerusRulesLength).toHex();
       
   546         }
       
   547 
       
   548         data += blockLen;
       
   549     }
       
   550 
       
   551 
       
   552     size_t numItems = offsetLength / (2 * sizeof(quint32));
       
   553     //qDebug() << "NUMITEMS: " << numItems;
       
   554 
       
   555     QTextCodec *codec = QTextCodec::codecForName(
       
   556         cd.m_codecForSource.isEmpty() ? QByteArray("Latin1") : cd.m_codecForSource);
       
   557     QTextCodec *utf8Codec = 0;
       
   558     if (codec->name() != "UTF-8")
       
   559         utf8Codec = QTextCodec::codecForName("UTF-8");
       
   560 
       
   561     QString strProN = QLatin1String("%n");
       
   562     QLocale::Language l;
       
   563     QLocale::Country c;
       
   564     Translator::languageAndCountry(translator.languageCode(), &l, &c);
       
   565     QStringList numerusForms;
       
   566     bool guessPlurals = true;
       
   567     if (getNumerusInfo(l, c, 0, &numerusForms))
       
   568         guessPlurals = (numerusForms.count() == 1);
       
   569 
       
   570     QString context, contextUtf8;
       
   571     bool contextIsSystem, contextIsUtf8, contextNeeds8Bit;
       
   572     QString sourcetext, sourcetextUtf8;
       
   573     bool sourcetextIsSystem, sourcetextIsUtf8, sourcetextNeeds8Bit;
       
   574     QString comment, commentUtf8;
       
   575     bool commentIsSystem, commentIsUtf8, commentNeeds8Bit;
       
   576     QStringList translations;
       
   577 
       
   578     for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
       
   579         //quint32 hash = read32(start);
       
   580         quint32 ro = read32(start + 4);
       
   581         //qDebug() << "\nHASH:" << hash;
       
   582         const uchar *m = messageArray + ro;
       
   583 
       
   584         for (;;) {
       
   585             uchar tag = read8(m++);
       
   586             //qDebug() << "Tag:" << tag << " ADDR: " << m;
       
   587             switch(tag) {
       
   588             case Tag_End:
       
   589                 goto end;
       
   590             case Tag_Translation: {
       
   591                 int len = read32(m);
       
   592                 if (len % 1) {
       
   593                     cd.appendError(QLatin1String("QM-Format error"));
       
   594                     return false;
       
   595                 }
       
   596                 m += 4;
       
   597                 QString str = QString((const QChar *)m, len/2);
       
   598                 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
       
   599                     for (int i = 0; i < str.length(); ++i)
       
   600                         str[i] = QChar((str.at(i).unicode() >> 8) +
       
   601                             ((str.at(i).unicode() << 8) & 0xff00));
       
   602                 }
       
   603                 translations << str;
       
   604                 m += len;
       
   605                 break;
       
   606             }
       
   607             case Tag_Obsolete1:
       
   608                 m += 4;
       
   609                 //qDebug() << "OBSOLETE";
       
   610                 break;
       
   611             case Tag_SourceText: {
       
   612                 quint32 len = read32(m);
       
   613                 m += 4;
       
   614                 //qDebug() << "SOURCE LEN: " << len;
       
   615                 //qDebug() << "SOURCE: " << QByteArray((const char*)m, len);
       
   616                 fromBytes((const char*)m, len, codec, utf8Codec,
       
   617                           &sourcetext, &sourcetextUtf8,
       
   618                           &sourcetextIsSystem, &sourcetextIsUtf8, &sourcetextNeeds8Bit);
       
   619                 m += len;
       
   620                 break;
       
   621             }
       
   622             case Tag_Context: {
       
   623                 quint32 len = read32(m);
       
   624                 m += 4;
       
   625                 //qDebug() << "CONTEXT LEN: " << len;
       
   626                 //qDebug() << "CONTEXT: " << QByteArray((const char*)m, len);
       
   627                 fromBytes((const char*)m, len, codec, utf8Codec,
       
   628                           &context, &contextUtf8,
       
   629                           &contextIsSystem, &contextIsUtf8, &contextNeeds8Bit);
       
   630                 m += len;
       
   631                 break;
       
   632             }
       
   633             case Tag_Comment: {
       
   634                 quint32 len = read32(m);
       
   635                 m += 4;
       
   636                 //qDebug() << "COMMENT LEN: " << len;
       
   637                 //qDebug() << "COMMENT: " << QByteArray((const char*)m, len);
       
   638                 fromBytes((const char*)m, len, codec, utf8Codec,
       
   639                           &comment, &commentUtf8,
       
   640                           &commentIsSystem, &commentIsUtf8, &commentNeeds8Bit);
       
   641                 m += len;
       
   642                 break;
       
   643             }
       
   644             default:
       
   645                 //qDebug() << "UNKNOWN TAG" << tag;
       
   646                 break;
       
   647             }
       
   648         }
       
   649     end:;
       
   650         TranslatorMessage msg;
       
   651         msg.setType(TranslatorMessage::Finished);
       
   652         if (translations.count() > 1) {
       
   653             // If guessPlurals is not false here, plural form discard messages
       
   654             // will be spewn out later.
       
   655             msg.setPlural(true);
       
   656         } else if (guessPlurals) {
       
   657             // This might cause false positives, so it is a fallback only.
       
   658             if (sourcetext.contains(strProN))
       
   659                 msg.setPlural(true);
       
   660         }
       
   661         msg.setTranslations(translations);
       
   662         translations.clear();
       
   663         if (contextNeeds8Bit || sourcetextNeeds8Bit || commentNeeds8Bit) {
       
   664             if (utf8Codec && contextIsUtf8 && sourcetextIsUtf8 && commentIsUtf8) {
       
   665                 // The message is utf-8, but file is not.
       
   666                 msg.setUtf8(true);
       
   667                 msg.setContext(contextUtf8);
       
   668                 msg.setSourceText(sourcetextUtf8);
       
   669                 msg.setComment(commentUtf8);
       
   670                 translator.append(msg);
       
   671                 continue;
       
   672             }
       
   673             if (!(contextIsSystem && sourcetextIsSystem && commentIsSystem)) {
       
   674                 cd.appendError(QLatin1String(
       
   675                         "Cannot read file with specified input codec"));
       
   676                 return false;
       
   677             }
       
   678             // The message is 8-bit in the file's encoding (utf-8 or not).
       
   679         }
       
   680         msg.setContext(context);
       
   681         msg.setSourceText(sourcetext);
       
   682         msg.setComment(comment);
       
   683         translator.append(msg);
       
   684     }
       
   685     return ok;
       
   686 }
       
   687 
       
   688 
       
   689 
       
   690 static bool containsStripped(const Translator &translator, const TranslatorMessage &msg)
       
   691 {
       
   692     foreach (const TranslatorMessage &tmsg, translator.messages())
       
   693         if (tmsg.sourceText() == msg.sourceText()
       
   694             && tmsg.context() == msg.context()
       
   695             && tmsg.comment().isEmpty())
       
   696         return true;
       
   697     return false;
       
   698 }
       
   699 
       
   700 static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)
       
   701 {
       
   702     Releaser releaser;
       
   703     QLocale::Language l;
       
   704     QLocale::Country c;
       
   705     Translator::languageAndCountry(translator.languageCode(), &l, &c);
       
   706     QByteArray rules;
       
   707     if (getNumerusInfo(l, c, &rules, 0))
       
   708         releaser.setNumerusRules(rules);
       
   709     releaser.setCodecName(translator.codecName());
       
   710 
       
   711     int finished = 0;
       
   712     int unfinished = 0;
       
   713     int untranslated = 0;
       
   714     int missingIds = 0;
       
   715     int droppedData = 0;
       
   716 
       
   717     for (int i = 0; i != translator.messageCount(); ++i) {
       
   718         const TranslatorMessage &msg = translator.message(i);
       
   719         TranslatorMessage::Type typ = msg.type();
       
   720         if (typ != TranslatorMessage::Obsolete) {
       
   721             if (cd.m_idBased && msg.id().isEmpty()) {
       
   722                 ++missingIds;
       
   723                 continue;
       
   724             }
       
   725             if (typ == TranslatorMessage::Unfinished) {
       
   726                 if (!cd.m_idBased && msg.translation().isEmpty()) {
       
   727                     ++untranslated;
       
   728                     continue;
       
   729                 } else {
       
   730                     if (cd.ignoreUnfinished())
       
   731                         continue;
       
   732                     ++unfinished;
       
   733                 }
       
   734             } else {
       
   735                 ++finished;
       
   736             }
       
   737             QStringList tlns = msg.translations();
       
   738             if (msg.type() == TranslatorMessage::Unfinished
       
   739                 && (cd.m_idBased || !cd.m_unTrPrefix.isEmpty()))
       
   740                 for (int j = 0; j < tlns.size(); ++j)
       
   741                     if (tlns.at(j).isEmpty())
       
   742                         tlns[j] = cd.m_unTrPrefix + msg.sourceText();
       
   743             if (cd.m_idBased) {
       
   744                 if (!msg.context().isEmpty() || !msg.comment().isEmpty())
       
   745                     ++droppedData;
       
   746                 releaser.insertIdBased(msg, tlns);
       
   747             } else {
       
   748                 // Drop the comment in (context, sourceText, comment),
       
   749                 // unless the context is empty,
       
   750                 // unless (context, sourceText, "") already exists or
       
   751                 // unless we already dropped the comment of (context,
       
   752                 // sourceText, comment0).
       
   753                 bool forceComment =
       
   754                         msg.comment().isEmpty()
       
   755                         || msg.context().isEmpty()
       
   756                         || containsStripped(translator, msg);
       
   757                 releaser.insert(msg, tlns, forceComment);
       
   758             }
       
   759         }
       
   760     }
       
   761 
       
   762     if (missingIds)
       
   763         cd.appendError(QCoreApplication::translate("LRelease",
       
   764             "Dropped %n message(s) which had no ID.", 0,
       
   765             QCoreApplication::CodecForTr, missingIds));
       
   766     if (droppedData)
       
   767         cd.appendError(QCoreApplication::translate("LRelease",
       
   768             "Excess context/disambiguation dropped from %n message(s).", 0,
       
   769             QCoreApplication::CodecForTr, droppedData));
       
   770 
       
   771     releaser.squeeze(cd.m_saveMode);
       
   772     bool saved = releaser.save(&dev);
       
   773     if (saved && cd.isVerbose()) {
       
   774         int generatedCount = finished + unfinished;
       
   775         cd.appendError(QCoreApplication::translate("LRelease",
       
   776             "    Generated %n translation(s) (%1 finished and %2 unfinished)\n", 0,
       
   777             QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished));
       
   778         if (untranslated)
       
   779             cd.appendError(QCoreApplication::translate("LRelease",
       
   780                 "    Ignored %n untranslated source text(s)\n", 0,
       
   781                 QCoreApplication::CodecForTr, untranslated));
       
   782     }
       
   783     return saved;
       
   784 }
       
   785 
       
   786 int initQM()
       
   787 {
       
   788     Translator::FileFormat format;
       
   789 
       
   790     format.extension = QLatin1String("qm");
       
   791     format.description = QObject::tr("Compiled Qt translations");
       
   792     format.fileType = Translator::FileFormat::TranslationBinary;
       
   793     format.priority = 0;
       
   794     format.loader = &loadQM;
       
   795     format.saver = &saveQM;
       
   796     Translator::registerFileFormat(format);
       
   797 
       
   798     return 1;
       
   799 }
       
   800 
       
   801 Q_CONSTRUCTOR_FUNCTION(initQM)
       
   802 
       
   803 QT_END_NAMESPACE