Ignore:
Timestamp:
May 5, 2011, 5:36:53 AM (14 years ago)
Author:
Dmitry A. Kuminov
Message:

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/tools/linguist/shared/po.cpp

    r651 r846  
    11/****************************************************************************
    22**
    3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
     3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
    44** All rights reserved.
    55** Contact: Nokia Corporation (qt-info@nokia.com)
     
    4646#include <QtCore/QHash>
    4747#include <QtCore/QString>
     48#include <QtCore/QTextCodec>
    4849#include <QtCore/QTextStream>
    4950
    5051#include <ctype.h>
    51 
    52 #define MAGIC_OBSOLETE_REFERENCE "Obsolete_PO_entries"
    5352
    5453// Uncomment if you wish to hard wrap long lines in .po files. Note that this
     
    202201
    203202public:
    204     QString id;
    205     QString context;
    206     QString tscomment;
    207     QString oldTscomment;
    208     QString lineNumber;
    209     QString fileName;
    210     QString references;
    211     QString translatorComments;
    212     QString automaticComments;
    213     QString msgId;
    214     QString oldMsgId;
    215     QStringList msgStr;
     203    QByteArray id;
     204    QByteArray context;
     205    QByteArray tscomment;
     206    QByteArray oldTscomment;
     207    QByteArray lineNumber;
     208    QByteArray fileName;
     209    QByteArray references;
     210    QByteArray translatorComments;
     211    QByteArray automaticComments;
     212    QByteArray msgId;
     213    QByteArray oldMsgId;
     214    QList<QByteArray> msgStr;
    216215    bool isPlural;
    217216    bool isFuzzy;
     
    220219
    221220
    222 static bool isTranslationLine(const QString &line)
    223 {
    224     return line.startsWith(QLatin1String("#~ msgstr"))
    225            || line.startsWith(QLatin1String("msgstr"));
    226 }
    227 
    228 static QString slurpEscapedString(const QStringList &lines, int & l,
    229         int offset, const QString &prefix, ConversionData &cd)
    230 {
    231     QString msg;
     221static bool isTranslationLine(const QByteArray &line)
     222{
     223    return line.startsWith("#~ msgstr") || line.startsWith("msgstr");
     224}
     225
     226static QByteArray slurpEscapedString(const QList<QByteArray> &lines, int &l,
     227        int offset, const QByteArray &prefix, ConversionData &cd)
     228{
     229    QByteArray msg;
    232230    int stoff;
    233231
    234232    for (; l < lines.size(); ++l) {
    235         const QString &line = lines.at(l);
     233        const QByteArray &line = lines.at(l);
    236234        if (line.isEmpty() || !line.startsWith(prefix))
    237235            break;
    238         while (line[offset].isSpace()) // No length check, as string has no trailing spaces.
     236        while (isspace(line[offset])) // No length check, as string has no trailing spaces.
    239237            offset++;
    240         if (line[offset].unicode() != '"')
     238        if (line[offset] != '"')
    241239            break;
    242240        offset++;
     
    244242            if (offset == line.length())
    245243                goto premature_eol;
    246             ushort c = line[offset++].unicode();
     244            uchar c = line[offset++];
    247245            if (c == '"') {
    248246                if (offset == line.length())
    249247                    break;
    250                 while (line[offset].isSpace())
     248                while (isspace(line[offset]))
    251249                    offset++;
    252                 if (line[offset++].unicode() != '"') {
     250                if (line[offset++] != '"') {
    253251                    cd.appendError(QString::fromLatin1(
    254252                            "PO parsing error: extra characters on line %1.")
     
    261259                if (offset == line.length())
    262260                    goto premature_eol;
    263                 c = line[offset++].unicode();
     261                c = line[offset++];
    264262                switch (c) {
    265263                case 'r':
    266                     msg += QLatin1Char('\r'); // Maybe just throw it away?
     264                    msg += '\r'; // Maybe just throw it away?
    267265                    break;
    268266                case 'n':
    269                     msg += QLatin1Char('\n');
     267                    msg += '\n';
    270268                    break;
    271269                case 't':
    272                     msg += QLatin1Char('\t');
     270                    msg += '\t';
    273271                    break;
    274272                case 'v':
    275                     msg += QLatin1Char('\v');
     273                    msg += '\v';
    276274                    break;
    277275                case 'a':
    278                     msg += QLatin1Char('\a');
     276                    msg += '\a';
    279277                    break;
    280278                case 'b':
    281                     msg += QLatin1Char('\b');
     279                    msg += '\b';
    282280                    break;
    283281                case 'f':
    284                     msg += QLatin1Char('\f');
     282                    msg += '\f';
    285283                    break;
    286284                case '"':
    287                     msg += QLatin1Char('"');
     285                    msg += '"';
    288286                    break;
    289287                case '\\':
    290                     msg += QLatin1Char('\\');
     288                    msg += '\\';
    291289                    break;
    292290                case '0':
     
    299297                case '7':
    300298                    stoff = offset - 1;
    301                     while ((c = line[offset].unicode()) >= '0' && c <= '7')
     299                    while ((c = line[offset]) >= '0' && c <= '7')
    302300                        if (++offset == line.length())
    303301                            goto premature_eol;
    304                     msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 8));
     302                    msg += line.mid(stoff, offset - stoff).toUInt(0, 8);
    305303                    break;
    306304                case 'x':
    307305                    stoff = offset;
    308                     while (isxdigit(line[offset].unicode()))
     306                    while (isxdigit(line[offset]))
    309307                        if (++offset == line.length())
    310308                            goto premature_eol;
    311                     msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 16));
     309                    msg += line.mid(stoff, offset - stoff).toUInt(0, 16);
    312310                    break;
    313311                default:
    314312                    cd.appendError(QString::fromLatin1(
    315313                            "PO parsing error: invalid escape '\\%1' (line %2).")
    316                             .arg(QChar(c)).arg(l + 1));
    317                     msg += QLatin1Char('\\');
    318                     msg += QChar(c);
     314                            .arg(QChar((uint)c)).arg(l + 1));
     315                    msg += '\\';
     316                    msg += c;
    319317                    break;
    320318                }
    321319            } else {
    322                 msg += QChar(c);
     320                msg += c;
    323321            }
    324322        }
     
    331329    cd.appendError(QString::fromLatin1(
    332330            "PO parsing error: premature end of line %1.").arg(l + 1));
    333     return QString();
    334 }
    335 
    336 static void slurpComment(QString &msg, const QStringList &lines, int & l)
    337 {
    338     const QChar newline = QLatin1Char('\n');
    339     QString prefix = lines.at(l);
     331    return QByteArray();
     332}
     333
     334static void slurpComment(QByteArray &msg, const QList<QByteArray> &lines, int & l)
     335{
     336    QByteArray prefix = lines.at(l);
    340337    for (int i = 1; ; i++) {
    341         if (prefix.at(i).unicode() != ' ') {
     338        if (prefix.at(i) != ' ') {
    342339            prefix.truncate(i);
    343340            break;
     
    345342    }
    346343    for (; l < lines.size(); ++l) {
    347         const QString &line = lines.at(l);
     344        const QByteArray &line = lines.at(l);
    348345        if (line.startsWith(prefix))
    349346            msg += line.mid(prefix.size());
    350         else if (line != QLatin1String("#"))
    351             break;
    352         msg += newline;
     347        else if (line != "#")
     348            break;
     349        msg += '\n';
    353350    }
    354351    --l;
    355352}
    356353
     354static void splitContext(QByteArray *comment, QByteArray *context)
     355{
     356    char *data = comment->data();
     357    int len = comment->size();
     358    int sep = -1, j = 0;
     359
     360    for (int i = 0; i < len; i++, j++) {
     361        if (data[i] == '~' && i + 1 < len)
     362            i++;
     363        else if (data[i] == '|')
     364            sep = j;
     365        data[j] = data[i];
     366    }
     367    if (sep >= 0) {
     368        QByteArray tmp = comment->mid(sep + 1, j - sep - 1);
     369        comment->truncate(sep);
     370        *context = *comment;
     371        *comment = tmp;
     372    } else {
     373        comment->truncate(j);
     374    }
     375}
     376
     377static QString makePoHeader(const QString &str)
     378{
     379    return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_'));
     380}
     381
     382static QByteArray QByteArrayList_join(const QList<QByteArray> &that, char sep)
     383{
     384    int totalLength = 0;
     385    const int size = that.size();
     386
     387    for (int i = 0; i < size; ++i)
     388        totalLength += that.at(i).size();
     389
     390    if (size > 0)
     391        totalLength += size - 1;
     392
     393    QByteArray res;
     394    if (totalLength == 0)
     395        return res;
     396    res.reserve(totalLength);
     397    for (int i = 0; i < that.size(); ++i) {
     398        if (i)
     399            res += sep;
     400        res += that.at(i);
     401    }
     402    return res;
     403}
     404
    357405bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd)
    358406{
    359     const QChar quote = QLatin1Char('"');
    360     const QChar newline = QLatin1Char('\n');
    361     QTextStream in(&dev);
    362     in.setCodec(cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource);
     407    QTextCodec *codec = QTextCodec::codecForName(
     408            cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource);
    363409    bool error = false;
    364410
     
    381427
    382428    // we need line based lookahead below.
    383     QStringList lines;
    384     while (!in.atEnd())
    385         lines.append(in.readLine().trimmed());
    386     lines.append(QString());
    387 
    388     int l = 0;
     429    QList<QByteArray> lines;
     430    while (!dev.atEnd())
     431        lines.append(dev.readLine().trimmed());
     432    lines.append(QByteArray());
     433
     434    int l = 0, lastCmtLine = -1;
     435    bool qtContexts = false;
    389436    PoItem item;
    390437    for (; l != lines.size(); ++l) {
    391         QString line = lines.at(l);
     438        QByteArray line = lines.at(l);
    392439        if (line.isEmpty())
    393440           continue;
    394441        if (isTranslationLine(line)) {
    395             bool isObsolete = line.startsWith(QLatin1String("#~ msgstr"));
    396             const QString prefix = QLatin1String(isObsolete ? "#~ " : "");
     442            bool isObsolete = line.startsWith("#~ msgstr");
     443            const QByteArray prefix = isObsolete ? "#~ " : "";
    397444            while (true) {
    398                 int idx = line.indexOf(QLatin1Char(' '), prefix.length());
    399                 QString str = slurpEscapedString(lines, l, idx, prefix, cd);
     445                int idx = line.indexOf(' ', prefix.length());
     446                QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd);
     447                item.msgStr.append(str);
     448                if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
     449                    break;
     450                ++l;
     451                line = lines.at(l);
     452            }
     453            if (item.msgId.isEmpty()) {
     454                QHash<QString, QByteArray> extras;
     455                QList<QByteArray> hdrOrder;
     456                QByteArray pluralForms;
     457                foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) {
     458                    if (hdr.isEmpty())
     459                        continue;
     460                    int idx = hdr.indexOf(':');
     461                    if (idx < 0) {
     462                        cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'")
     463                            .arg(QString::fromLatin1(hdr)));
     464                        error = true;
     465                        break;
     466                    }
     467                    QByteArray hdrName = hdr.left(idx).trimmed();
     468                    QByteArray hdrValue = hdr.mid(idx + 1).trimmed();
     469                    hdrOrder << hdrName;
     470                    if (hdrName == "X-Language") {
     471                        translator.setLanguageCode(QString::fromLatin1(hdrValue));
     472                    } else if (hdrName == "X-Source-Language") {
     473                        translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
     474                    } else if (hdrName == "X-Qt-Contexts") {
     475                        qtContexts = (hdrValue == "true");
     476                    } else if (hdrName == "Plural-Forms") {
     477                        pluralForms  = hdrValue;
     478                    } else if (hdrName == "MIME-Version") {
     479                        // just assume it is 1.0
     480                    } else if (hdrName == "Content-Type") {
     481                        if (cd.m_codecForSource.isEmpty()) {
     482                            if (!hdrValue.startsWith("text/plain; charset=")) {
     483                                cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'")
     484                                    .arg(QString::fromLatin1(hdrValue)));
     485                                error = true;
     486                                // This will avoid a flood of conversion errors.
     487                                codec = QTextCodec::codecForName("latin1");
     488                            } else {
     489                                QByteArray cod = hdrValue.mid(20);
     490                                QTextCodec *cdc = QTextCodec::codecForName(cod);
     491                                if (!cdc) {
     492                                    cd.appendError(QString::fromLatin1("Unsupported codec '%1'")
     493                                            .arg(QString::fromLatin1(cod)));
     494                                    error = true;
     495                                    // This will avoid a flood of conversion errors.
     496                                    codec = QTextCodec::codecForName("latin1");
     497                                } else {
     498                                    codec = cdc;
     499                                }
     500                            }
     501                        }
     502                    } else if (hdrName == "Content-Transfer-Encoding") {
     503                        if (hdrValue != "8bit") {
     504                            cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'")
     505                                .arg(QString::fromLatin1(hdrValue)));
     506                            return false;
     507                        }
     508                    } else if (hdrName == "X-Virgin-Header") {
     509                        // legacy
     510                    } else {
     511                        extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue;
     512                    }
     513                }
     514                if (!pluralForms.isEmpty()) {
     515                    if (translator.languageCode().isEmpty()) {
     516                        extras[makePoHeader(QLatin1String("Plural-Forms"))] = pluralForms;
     517                    } else {
     518                         // FIXME: have fun with making a consistency check ...
     519                    }
     520                }
     521                // Eliminate the field if only headers we added are present in standard order.
     522                // Keep in sync with savePO
     523                static const char * const dfltHdrs[] = {
     524                    "MIME-Version", "Content-Type", "Content-Transfer-Encoding",
     525                    "Plural-Forms", "X-Language", "X-Source-Language", "X-Qt-Contexts"
     526                };
     527                uint cdh = 0;
     528                for (int cho = 0; cho < hdrOrder.length(); cho++) {
     529                    for (;; cdh++) {
     530                        if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) {
     531                            extras[QLatin1String("po-headers")] =
     532                                    QByteArrayList_join(hdrOrder, ',');
     533                            goto doneho;
     534                        }
     535                        if (hdrOrder.at(cho) == dfltHdrs[cdh]) {
     536                            cdh++;
     537                            break;
     538                        }
     539                    }
     540                }
     541              doneho:
     542                if (lastCmtLine != -1)
     543                    extras[QLatin1String("po-header_comment")] =
     544                            QByteArrayList_join(lines.mid(0, lastCmtLine + 1), '\n');
     545                for (QHash<QString, QByteArray>::ConstIterator it = extras.constBegin(),
     546                                                               end = extras.constEnd();
     547                     it != end; ++it)
     548                    translator.setExtra(it.key(), codec->toUnicode(it.value()));
     549                item = PoItem();
     550                continue;
     551            }
     552            // build translator message
     553            TranslatorMessage msg;
     554            msg.setContext(codec->toUnicode(item.context));
     555            if (!item.references.isEmpty()) {
     556                QString xrefs;
     557                foreach (const QString &ref,
     558                         codec->toUnicode(item.references).split(
     559                                 QRegExp(QLatin1String("\\s")), QString::SkipEmptyParts)) {
     560                    int pos = ref.indexOf(QLatin1Char(':'));
     561                    int lpos = ref.lastIndexOf(QLatin1Char(':'));
     562                    if (pos != -1 && pos == lpos) {
     563                        bool ok;
     564                        int lno = ref.mid(pos + 1).toInt(&ok);
     565                        if (ok) {
     566                            msg.addReference(ref.left(pos), lno);
     567                            continue;
     568                        }
     569                    }
     570                    if (!xrefs.isEmpty())
     571                        xrefs += QLatin1Char(' ');
     572                    xrefs += ref;
     573                }
     574                if (!xrefs.isEmpty())
     575                    item.extra[QLatin1String("po-references")] = xrefs;
     576            }
     577            msg.setId(codec->toUnicode(item.id));
     578            msg.setSourceText(codec->toUnicode(item.msgId));
     579            msg.setOldSourceText(codec->toUnicode(item.oldMsgId));
     580            msg.setComment(codec->toUnicode(item.tscomment));
     581            msg.setOldComment(codec->toUnicode(item.oldTscomment));
     582            msg.setExtraComment(codec->toUnicode(item.automaticComments));
     583            msg.setTranslatorComment(codec->toUnicode(item.translatorComments));
     584            msg.setPlural(item.isPlural || item.msgStr.size() > 1);
     585            QStringList translations;
     586            foreach (const QByteArray &bstr, item.msgStr) {
     587                QString str = codec->toUnicode(bstr);
    400588                str.replace(QChar(Translator::TextVariantSeparator),
    401589                            QChar(Translator::BinaryVariantSeparator));
    402                 item.msgStr.append(str);
    403                 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
    404                     break;
    405                 ++l;
    406                 line = lines.at(l);
    407             }
    408             if (item.msgId.isEmpty()) {
    409                 QRegExp rx(QLatin1String("\\bX-Language: ([^\n]*)\n"));
    410                 int idx = rx.indexIn(item.msgStr.first());
    411                 if (idx >= 0) {
    412                     translator.setLanguageCode(rx.cap(1));
    413                     item.msgStr.first().remove(idx, rx.matchedLength());
    414                 }
    415                 QRegExp rx2(QLatin1String("\\bX-Source-Language: ([^\n]*)\n"));
    416                 int idx2 = rx2.indexIn(item.msgStr.first());
    417                 if (idx2 >= 0) {
    418                     translator.setSourceLanguageCode(rx2.cap(1));
    419                     item.msgStr.first().remove(idx2, rx2.matchedLength());
    420                 }
    421                 if (item.msgStr.first().indexOf(
    422                         QRegExp(QLatin1String("\\bX-Virgin-Header:[^\n]*\n"))) >= 0) {
    423                     item = PoItem();
    424                     continue;
    425                 }
    426             }
    427             // build translator message
    428             TranslatorMessage msg;
    429             msg.setContext(item.context);
    430             if (!item.references.isEmpty()) {
    431                 foreach (const QString &ref,
    432                          item.references.split(QRegExp(QLatin1String("\\s")),
    433                                                QString::SkipEmptyParts)) {
    434                     int pos = ref.lastIndexOf(QLatin1Char(':'));
    435                     if (pos != -1)
    436                         msg.addReference(ref.left(pos), ref.mid(pos + 1).toInt());
    437                 }
    438             } else if (isObsolete) {
    439                 msg.setFileName(QLatin1String(MAGIC_OBSOLETE_REFERENCE));
    440             }
    441             msg.setId(item.id);
    442             msg.setSourceText(item.msgId);
    443             msg.setOldSourceText(item.oldMsgId);
    444             msg.setComment(item.tscomment);
    445             msg.setOldComment(item.oldTscomment);
    446             msg.setExtraComment(item.automaticComments);
    447             msg.setTranslatorComment(item.translatorComments);
    448             msg.setPlural(item.isPlural || item.msgStr.size() > 1);
    449             msg.setTranslations(item.msgStr);
     590                translations << str;
     591            }
     592            msg.setTranslations(translations);
    450593            if (isObsolete)
    451594                msg.setType(TranslatorMessage::Obsolete);
    452             else if (item.isFuzzy)
     595            else if (item.isFuzzy || (!msg.sourceText().isEmpty() && !msg.isTranslated()))
    453596                msg.setType(TranslatorMessage::Unfinished);
    454597            else
     
    461604            translator.append(msg);
    462605            item = PoItem();
    463         } else if (line.startsWith(QLatin1Char('#'))) {
    464             switch(line.size() < 2 ? 0 : line.at(1).unicode()) {
     606        } else if (line.startsWith('#')) {
     607            switch (line.size() < 2 ? 0 : line.at(1)) {
    465608                case ':':
    466609                    item.references += line.mid(3);
    467                     item.references += newline;
     610                    item.references += '\n';
    468611                    break;
    469612                case ',': {
    470613                    QStringList flags =
    471                             line.mid(2).split(QRegExp(QLatin1String("[, ]")),
    472                                               QString::SkipEmptyParts);
     614                            QString::fromLatin1(line.mid(2)).split(
     615                                    QRegExp(QLatin1String("[, ]")), QString::SkipEmptyParts);
    473616                    if (flags.removeOne(QLatin1String("fuzzy")))
    474617                        item.isFuzzy = true;
     618                    flags.removeOne(QLatin1String("qt-format"));
    475619                    TranslatorMessage::ExtraData::const_iterator it =
    476620                            item.extra.find(QLatin1String("po-flags"));
     
    482626                }
    483627                case 0:
    484                     item.translatorComments += newline;
     628                    item.translatorComments += '\n';
    485629                    break;
    486630                case ' ':
     
    488632                    break;
    489633                case '.':
    490                     if (line.startsWith(QLatin1String("#. ts-context "))) {
     634                    if (line.startsWith("#. ts-context ")) { // legacy
    491635                        item.context = line.mid(14);
    492                     } else if (line.startsWith(QLatin1String("#. ts-id "))) {
     636                    } else if (line.startsWith("#. ts-id ")) {
    493637                        item.id = line.mid(9);
    494638                    } else {
    495639                        item.automaticComments += line.mid(3);
    496                         item.automaticComments += newline;
     640                        item.automaticComments += '\n';
    497641                    }
    498642                    break;
    499643                case '|':
    500                     if (line.startsWith(QLatin1String("#| msgid "))) {
    501                         item.oldMsgId = slurpEscapedString(lines, l, 9, QLatin1String("#| "), cd);
    502                     } else if (line.startsWith(QLatin1String("#| msgid_plural "))) {
    503                         QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#| "), cd);
     644                    if (line.startsWith("#| msgid ")) {
     645                        item.oldMsgId = slurpEscapedString(lines, l, 9, "#| ", cd);
     646                    } else if (line.startsWith("#| msgid_plural ")) {
     647                        QByteArray extra = slurpEscapedString(lines, l, 16, "#| ", cd);
    504648                        if (extra != item.oldMsgId)
    505                             item.extra[QLatin1String("po-old_msgid_plural")] = extra;
    506                     } else if (line.startsWith(QLatin1String("#| msgctxt "))) {
    507                         item.oldTscomment = slurpEscapedString(lines, l, 11, QLatin1String("#| "), cd);
     649                            item.extra[QLatin1String("po-old_msgid_plural")] =
     650                                    codec->toUnicode(extra);
     651                    } else if (line.startsWith("#| msgctxt ")) {
     652                        item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd);
     653                        if (qtContexts)
     654                            splitContext(&item.oldTscomment, &item.context);
    508655                    } else {
    509                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
    510                             .arg(l + 1).arg(lines[l]));
     656                        cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
     657                            .arg(l + 1).arg(codec->toUnicode(lines[l])));
    511658                        error = true;
    512659                    }
    513660                    break;
    514661                case '~':
    515                     if (line.startsWith(QLatin1String("#~ msgid "))) {
    516                         item.msgId = slurpEscapedString(lines, l, 9, QLatin1String("#~ "), cd);
    517                     } else if (line.startsWith(QLatin1String("#~ msgid_plural "))) {
    518                         QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#~ "), cd);
     662                    if (line.startsWith("#~ msgid ")) {
     663                        item.msgId = slurpEscapedString(lines, l, 9, "#~ ", cd);
     664                    } else if (line.startsWith("#~ msgid_plural ")) {
     665                        QByteArray extra = slurpEscapedString(lines, l, 16, "#~ ", cd);
    519666                        if (extra != item.msgId)
    520                             item.extra[QLatin1String("po-msgid_plural")] = extra;
     667                            item.extra[QLatin1String("po-msgid_plural")] =
     668                                    codec->toUnicode(extra);
    521669                        item.isPlural = true;
    522                     } else if (line.startsWith(QLatin1String("#~ msgctxt "))) {
    523                         item.tscomment = slurpEscapedString(lines, l, 11, QLatin1String("#~ "), cd);
     670                    } else if (line.startsWith("#~ msgctxt ")) {
     671                        item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd);
     672                        if (qtContexts)
     673                            splitContext(&item.tscomment, &item.context);
    524674                    } else {
    525                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
    526                             .arg(l + 1).arg(lines[l]));
     675                        cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
     676                            .arg(l + 1).arg(codec->toUnicode(lines[l])));
    527677                        error = true;
    528678                    }
    529679                    break;
    530680                default:
    531                     cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
    532                         .arg(l + 1).arg(lines[l]));
     681                    cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
     682                        .arg(l + 1).arg(codec->toUnicode(lines[l])));
    533683                    error = true;
    534684                    break;
    535685            }
    536         } else if (line.startsWith(QLatin1String("msgctxt "))) {
    537             item.tscomment = slurpEscapedString(lines, l, 8, QString(), cd);
    538         } else if (line.startsWith(QLatin1String("msgid "))) {
    539             item.msgId = slurpEscapedString(lines, l, 6, QString(), cd);
    540         } else if (line.startsWith(QLatin1String("msgid_plural "))) {
    541             QString extra = slurpEscapedString(lines, l, 13, QString(), cd);
     686            lastCmtLine = l;
     687        } else if (line.startsWith("msgctxt ")) {
     688            item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
     689            if (qtContexts)
     690                splitContext(&item.tscomment, &item.context);
     691        } else if (line.startsWith("msgid ")) {
     692            item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
     693        } else if (line.startsWith("msgid_plural ")) {
     694            QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
    542695            if (extra != item.msgId)
    543                 item.extra[QLatin1String("po-msgid_plural")] = extra;
     696                item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra);
    544697            item.isPlural = true;
    545698        } else {
    546             cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n"))
    547                 .arg(l + 1).arg(lines[l]));
     699            cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'"))
     700                .arg(l + 1).arg(codec->toUnicode(lines[l])));
    548701            error = true;
    549702        }
     
    552705}
    553706
     707static void addPoHeader(Translator::ExtraData &headers, QStringList &hdrOrder,
     708                        const char *name, const QString &value)
     709{
     710    QString qName = QLatin1String(name);
     711    if (!hdrOrder.contains(qName))
     712        hdrOrder << qName;
     713    headers[makePoHeader(qName)] = value;
     714}
     715
     716static QString escapeComment(const QString &in, bool escape)
     717{
     718    QString out = in;
     719    if (escape) {
     720        out.replace(QLatin1Char('~'), QLatin1String("~~"));
     721        out.replace(QLatin1Char('|'), QLatin1String("~|"));
     722    }
     723    return out;
     724}
     725
    554726bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd)
    555727{
     728    QString str_format = QLatin1String("-format");
     729
    556730    bool ok = true;
    557731    QTextStream out(&dev);
    558732    out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec);
    559733
    560     bool first = true;
    561     if (translator.messages().isEmpty() || !translator.messages().first().sourceText().isEmpty()) {
    562         out <<
    563             "# SOME DESCRIPTIVE TITLE.\n"
    564             "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n"
    565             "# This file is distributed under the same license as the PACKAGE package.\n"
    566             "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
    567             "#\n"
    568             "#, fuzzy\n"
    569             "msgid \"\"\n"
    570             "msgstr \"\"\n"
    571             "\"X-Virgin-Header: remove this line if you change anything in the header.\\n\"\n";
    572         if (!translator.languageCode().isEmpty())
    573             out << "\"X-Language: " << translator.languageCode() << "\\n\"\n";
    574         if (!translator.sourceLanguageCode().isEmpty())
    575             out << "\"X-Source-Language: " << translator.sourceLanguageCode() << "\\n\"\n";
    576         first = false;
    577     }
     734    bool qtContexts = false;
     735    foreach (const TranslatorMessage &msg, translator.messages())
     736        if (!msg.context().isEmpty()) {
     737            qtContexts = true;
     738            break;
     739        }
     740
     741    QString cmt = translator.extra(QLatin1String("po-header_comment"));
     742    if (!cmt.isEmpty())
     743        out << cmt << '\n';
     744    out << "msgid \"\"\n";
     745    Translator::ExtraData headers = translator.extras();
     746    QStringList hdrOrder = translator.extra(QLatin1String("po-headers")).split(
     747            QLatin1Char(','), QString::SkipEmptyParts);
     748    // Keep in sync with loadPO
     749    addPoHeader(headers, hdrOrder, "MIME-Version", QLatin1String("1.0"));
     750    addPoHeader(headers, hdrOrder, "Content-Type",
     751                QLatin1String("text/plain; charset=" + out.codec()->name()));
     752    addPoHeader(headers, hdrOrder, "Content-Transfer-Encoding", QLatin1String("8bit"));
     753    if (!translator.languageCode().isEmpty()) {
     754        QLocale::Language l;
     755        QLocale::Country c;
     756        Translator::languageAndCountry(translator.languageCode(), &l, &c);
     757        const char *gettextRules;
     758        if (getNumerusInfo(l, c, 0, 0, &gettextRules))
     759            addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules));
     760        addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode());
     761    }
     762    if (!translator.sourceLanguageCode().isEmpty())
     763        addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode());
     764    if (qtContexts)
     765        addPoHeader(headers, hdrOrder, "X-Qt-Contexts", QLatin1String("true"));
     766    QString hdrStr;
     767    foreach (const QString &hdr, hdrOrder) {
     768        hdrStr += hdr;
     769        hdrStr += QLatin1String(": ");
     770        hdrStr += headers.value(makePoHeader(hdr));
     771        hdrStr += QLatin1Char('\n');
     772    }
     773    out << poEscapedString(QString(), QString::fromLatin1("msgstr"), true, hdrStr);
     774
    578775    foreach (const TranslatorMessage &msg, translator.messages()) {
    579         if (!first)
    580             out << endl;
     776        out << endl;
    581777
    582778        if (!msg.translatorComment().isEmpty())
     
    586782            out << poEscapedLines(QLatin1String("#."), true, msg.extraComment());
    587783
    588         if (!msg.context().isEmpty())
    589             out << QLatin1String("#. ts-context ") << msg.context() << '\n';
    590784        if (!msg.id().isEmpty())
    591785            out << QLatin1String("#. ts-id ") << msg.id() << '\n';
    592786
    593         if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) {
     787        QString xrefs = msg.extra(QLatin1String("po-references"));
     788        if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) {
    594789            QStringList refs;
    595790            foreach (const TranslatorMessage::Reference &ref, msg.allReferences())
    596791                refs.append(QString(QLatin1String("%2:%1"))
    597792                                    .arg(ref.lineNumber()).arg(ref.fileName()));
     793            if (!xrefs.isEmpty())
     794                refs << xrefs;
    598795            out << poWrappedEscapedLines(QLatin1String("#:"), true, refs.join(QLatin1String(" ")));
    599796        }
    600797
    601798        bool noWrap = false;
     799        bool skipFormat = false;
    602800        QStringList flags;
    603         if (msg.type() == TranslatorMessage::Unfinished)
     801        if (msg.type() == TranslatorMessage::Unfinished && msg.isTranslated())
    604802            flags.append(QLatin1String("fuzzy"));
    605803        TranslatorMessage::ExtraData::const_iterator itr =
    606804                msg.extras().find(QLatin1String("po-flags"));
    607805        if (itr != msg.extras().end()) {
    608             if (itr->split(QLatin1String(", ")).contains(QLatin1String("no-wrap")))
     806            QStringList atoms = itr->split(QLatin1String(", "));
     807            foreach (const QString &atom, atoms)
     808                if (atom.endsWith(str_format)) {
     809                    skipFormat = true;
     810                    break;
     811                }
     812            if (atoms.contains(QLatin1String("no-wrap")))
    609813                noWrap = true;
    610814            flags.append(*itr);
    611815        }
     816        if (!skipFormat) {
     817            QString source = msg.sourceText();
     818            // This is fuzzy logic, as we don't know whether the string is
     819            // actually used with QString::arg().
     820            for (int off = 0; (off = source.indexOf(QLatin1Char('%'), off)) >= 0; ) {
     821                if (++off >= source.length())
     822                    break;
     823                if (source.at(off) == QLatin1Char('n') || source.at(off).isDigit()) {
     824                    flags.append(QLatin1String("qt-format"));
     825                    break;
     826                }
     827            }
     828        }
    612829        if (!flags.isEmpty())
    613830            out << "#, " << flags.join(QLatin1String(", ")) << '\n';
     
    615832        QString prefix = QLatin1String("#| ");
    616833        if (!msg.oldComment().isEmpty())
    617             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.oldComment());
     834            out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
     835                                   escapeComment(msg.oldComment(), qtContexts));
    618836        if (!msg.oldSourceText().isEmpty())
    619837            out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText());
     
    622840            out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
    623841        prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : "");
    624         if (!msg.comment().isEmpty())
    625             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.comment());
     842        if (!msg.context().isEmpty())
     843            out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
     844                                   escapeComment(msg.context(), true) + QLatin1Char('|')
     845                                   + escapeComment(msg.comment(), true));
     846        else if (!msg.comment().isEmpty())
     847            out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
     848                                   escapeComment(msg.comment(), qtContexts));
    626849        out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText());
    627850        if (!msg.isPlural()) {
    628851            QString transl = msg.translation();
    629             if (first) {
    630                 transl.remove(QRegExp(QLatin1String("\\bX-Language:[^\n]*\n")));
    631                 if (!translator.languageCode().isEmpty())
    632                     transl += QLatin1String("X-Language: ") + translator.languageCode() + QLatin1Char('\n');
    633             }
     852            transl.replace(QChar(Translator::BinaryVariantSeparator),
     853                           QChar(Translator::TextVariantSeparator));
    634854            out << poEscapedString(prefix, QLatin1String("msgstr"), noWrap, transl);
    635855        } else {
     
    647867            }
    648868        }
    649         first = false;
    650869    }
    651870    return ok;
     871}
     872
     873static bool savePOT(const Translator &translator, QIODevice &dev, ConversionData &cd)
     874{
     875    Translator ttor = translator;
     876    ttor.dropTranslations();
     877    return savePO(ttor, dev, cd);
    652878}
    653879
     
    662888    format.priority = 1;
    663889    Translator::registerFileFormat(format);
     890    format.extension = QLatin1String("pot");
     891    format.description = QObject::tr("GNU Gettext localization template files");
     892    format.loader = &loadPO;
     893    format.saver = &savePOT;
     894    format.fileType = Translator::FileFormat::TranslationSource;
     895    format.priority = -1;
     896    Translator::registerFileFormat(format);
    664897    return 1;
    665898}
Note: See TracChangeset for help on using the changeset viewer.