tools/linguist/shared/po.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
equal deleted inserted replaced
30:5dc02b23752f 33:3e2da88830cd
   351         msg += '\n';
   351         msg += '\n';
   352     }
   352     }
   353     --l;
   353     --l;
   354 }
   354 }
   355 
   355 
       
   356 static void splitContext(QByteArray *comment, QByteArray *context)
       
   357 {
       
   358     char *data = comment->data();
       
   359     int len = comment->size();
       
   360     int sep = -1, j = 0;
       
   361 
       
   362     for (int i = 0; i < len; i++, j++) {
       
   363         if (data[i] == '~' && i + 1 < len)
       
   364             i++;
       
   365         else if (data[i] == '|')
       
   366             sep = j;
       
   367         data[j] = data[i];
       
   368     }
       
   369     if (sep >= 0) {
       
   370         QByteArray tmp = comment->mid(sep + 1, j - sep - 1);
       
   371         comment->truncate(sep);
       
   372         *context = *comment;
       
   373         *comment = tmp;
       
   374     } else {
       
   375         comment->truncate(j);
       
   376     }
       
   377 }
       
   378 
   356 static QString makePoHeader(const QString &str)
   379 static QString makePoHeader(const QString &str)
   357 {
   380 {
   358     return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_'));
   381     return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_'));
   359 }
   382 }
   360 
   383 
   409     while (!dev.atEnd())
   432     while (!dev.atEnd())
   410         lines.append(dev.readLine().trimmed());
   433         lines.append(dev.readLine().trimmed());
   411     lines.append(QByteArray());
   434     lines.append(QByteArray());
   412 
   435 
   413     int l = 0, lastCmtLine = -1;
   436     int l = 0, lastCmtLine = -1;
       
   437     bool qtContexts = false;
   414     PoItem item;
   438     PoItem item;
   415     for (; l != lines.size(); ++l) {
   439     for (; l != lines.size(); ++l) {
   416         QByteArray line = lines.at(l);
   440         QByteArray line = lines.at(l);
   417         if (line.isEmpty())
   441         if (line.isEmpty())
   418            continue;
   442            continue;
   435                 foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) {
   459                 foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) {
   436                     if (hdr.isEmpty())
   460                     if (hdr.isEmpty())
   437                         continue;
   461                         continue;
   438                     int idx = hdr.indexOf(':');
   462                     int idx = hdr.indexOf(':');
   439                     if (idx < 0) {
   463                     if (idx < 0) {
   440                         cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'\n")
   464                         cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'")
   441                             .arg(QString::fromLatin1(hdr)));
   465                             .arg(QString::fromLatin1(hdr)));
   442                         error = true;
   466                         error = true;
   443                         break;
   467                         break;
   444                     }
   468                     }
   445                     QByteArray hdrName = hdr.left(idx).trimmed();
   469                     QByteArray hdrName = hdr.left(idx).trimmed();
   447                     hdrOrder << hdrName;
   471                     hdrOrder << hdrName;
   448                     if (hdrName == "X-Language") {
   472                     if (hdrName == "X-Language") {
   449                         translator.setLanguageCode(QString::fromLatin1(hdrValue));
   473                         translator.setLanguageCode(QString::fromLatin1(hdrValue));
   450                     } else if (hdrName == "X-Source-Language") {
   474                     } else if (hdrName == "X-Source-Language") {
   451                         translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
   475                         translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
       
   476                     } else if (hdrName == "X-Qt-Contexts") {
       
   477                         qtContexts = (hdrValue == "true");
   452                     } else if (hdrName == "Plural-Forms") {
   478                     } else if (hdrName == "Plural-Forms") {
   453                         pluralForms  = hdrValue;
   479                         pluralForms  = hdrValue;
   454                     } else if (hdrName == "MIME-Version") {
   480                     } else if (hdrName == "MIME-Version") {
   455                         // just assume it is 1.0
   481                         // just assume it is 1.0
   456                     } else if (hdrName == "Content-Type") {
   482                     } else if (hdrName == "Content-Type") {
   457                         if (cd.m_codecForSource.isEmpty()) {
   483                         if (cd.m_codecForSource.isEmpty()) {
   458                             if (!hdrValue.startsWith("text/plain; charset=")) {
   484                             if (!hdrValue.startsWith("text/plain; charset=")) {
   459                                 cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'\n")
   485                                 cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'")
   460                                     .arg(QString::fromLatin1(hdrValue)));
   486                                     .arg(QString::fromLatin1(hdrValue)));
   461                                 error = true;
   487                                 error = true;
   462                                 // This will avoid a flood of conversion errors.
   488                                 // This will avoid a flood of conversion errors.
   463                                 codec = QTextCodec::codecForName("latin1");
   489                                 codec = QTextCodec::codecForName("latin1");
   464                             } else {
   490                             } else {
   465                                 QByteArray cod = hdrValue.mid(20);
   491                                 QByteArray cod = hdrValue.mid(20);
   466                                 QTextCodec *cdc = QTextCodec::codecForName(cod);
   492                                 QTextCodec *cdc = QTextCodec::codecForName(cod);
   467                                 if (!cdc) {
   493                                 if (!cdc) {
   468                                     cd.appendError(QString::fromLatin1("Unsupported codec '%1'\n")
   494                                     cd.appendError(QString::fromLatin1("Unsupported codec '%1'")
   469                                             .arg(QString::fromLatin1(cod)));
   495                                             .arg(QString::fromLatin1(cod)));
   470                                     error = true;
   496                                     error = true;
   471                                     // This will avoid a flood of conversion errors.
   497                                     // This will avoid a flood of conversion errors.
   472                                     codec = QTextCodec::codecForName("latin1");
   498                                     codec = QTextCodec::codecForName("latin1");
   473                                 } else {
   499                                 } else {
   475                                 }
   501                                 }
   476                             }
   502                             }
   477                         }
   503                         }
   478                     } else if (hdrName == "Content-Transfer-Encoding") {
   504                     } else if (hdrName == "Content-Transfer-Encoding") {
   479                         if (hdrValue != "8bit") {
   505                         if (hdrValue != "8bit") {
   480                             cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'\n")
   506                             cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'")
   481                                 .arg(QString::fromLatin1(hdrValue)));
   507                                 .arg(QString::fromLatin1(hdrValue)));
   482                             return false;
   508                             return false;
   483                         }
   509                         }
   484                     } else if (hdrName == "X-Virgin-Header") {
   510                     } else if (hdrName == "X-Virgin-Header") {
   485                         // legacy
   511                         // legacy
   496                 }
   522                 }
   497                 // Eliminate the field if only headers we added are present in standard order.
   523                 // Eliminate the field if only headers we added are present in standard order.
   498                 // Keep in sync with savePO
   524                 // Keep in sync with savePO
   499                 static const char * const dfltHdrs[] = {
   525                 static const char * const dfltHdrs[] = {
   500                     "MIME-Version", "Content-Type", "Content-Transfer-Encoding",
   526                     "MIME-Version", "Content-Type", "Content-Transfer-Encoding",
   501                     "Plural-Forms", "X-Language", "X-Source-Language"
   527                     "Plural-Forms", "X-Language", "X-Source-Language", "X-Qt-Contexts"
   502                 };
   528                 };
   503                 uint cdh = 0;
   529                 uint cdh = 0;
   504                 for (int cho = 0; cho < hdrOrder.length(); cho++) {
   530                 for (int cho = 0; cho < hdrOrder.length(); cho++) {
   505                     for (;; cdh++) {
   531                     for (;; cdh++) {
   506                         if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) {
   532                         if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) {
   594                     break;
   620                     break;
   595                 case ' ':
   621                 case ' ':
   596                     slurpComment(item.translatorComments, lines, l);
   622                     slurpComment(item.translatorComments, lines, l);
   597                     break;
   623                     break;
   598                 case '.':
   624                 case '.':
   599                     if (line.startsWith("#. ts-context ")) {
   625                     if (line.startsWith("#. ts-context ")) { // legacy
   600                         item.context = line.mid(14);
   626                         item.context = line.mid(14);
   601                     } else if (line.startsWith("#. ts-id ")) {
   627                     } else if (line.startsWith("#. ts-id ")) {
   602                         item.id = line.mid(9);
   628                         item.id = line.mid(9);
   603                     } else {
   629                     } else {
   604                         item.automaticComments += line.mid(3);
   630                         item.automaticComments += line.mid(3);
   613                         if (extra != item.oldMsgId)
   639                         if (extra != item.oldMsgId)
   614                             item.extra[QLatin1String("po-old_msgid_plural")] =
   640                             item.extra[QLatin1String("po-old_msgid_plural")] =
   615                                     codec->toUnicode(extra);
   641                                     codec->toUnicode(extra);
   616                     } else if (line.startsWith("#| msgctxt ")) {
   642                     } else if (line.startsWith("#| msgctxt ")) {
   617                         item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd);
   643                         item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd);
       
   644                         if (qtContexts)
       
   645                             splitContext(&item.oldTscomment, &item.context);
   618                     } else {
   646                     } else {
   619                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
   647                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
   620                             .arg(l + 1).arg(codec->toUnicode(lines[l])));
   648                             .arg(l + 1).arg(codec->toUnicode(lines[l])));
   621                         error = true;
   649                         error = true;
   622                     }
   650                     }
   623                     break;
   651                     break;
   624                 case '~':
   652                 case '~':
   631                                     codec->toUnicode(extra);
   659                                     codec->toUnicode(extra);
   632                         item.isPlural = true;
   660                         item.isPlural = true;
   633                     } else if (line.startsWith("#~ msgctxt ")) {
   661                     } else if (line.startsWith("#~ msgctxt ")) {
   634                         item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd);
   662                         item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd);
   635                     } else {
   663                     } else {
   636                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
   664                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
   637                             .arg(l + 1).arg(codec->toUnicode(lines[l])));
   665                             .arg(l + 1).arg(codec->toUnicode(lines[l])));
   638                         error = true;
   666                         error = true;
   639                     }
   667                     }
   640                     break;
   668                     break;
   641                 default:
   669                 default:
   642                     cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
   670                     cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
   643                         .arg(l + 1).arg(codec->toUnicode(lines[l])));
   671                         .arg(l + 1).arg(codec->toUnicode(lines[l])));
   644                     error = true;
   672                     error = true;
   645                     break;
   673                     break;
   646             }
   674             }
   647             lastCmtLine = l;
   675             lastCmtLine = l;
   648         } else if (line.startsWith("msgctxt ")) {
   676         } else if (line.startsWith("msgctxt ")) {
   649             item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
   677             item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
       
   678             if (qtContexts)
       
   679                 splitContext(&item.tscomment, &item.context);
   650         } else if (line.startsWith("msgid ")) {
   680         } else if (line.startsWith("msgid ")) {
   651             item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
   681             item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
   652         } else if (line.startsWith("msgid_plural ")) {
   682         } else if (line.startsWith("msgid_plural ")) {
   653             QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
   683             QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
   654             if (extra != item.msgId)
   684             if (extra != item.msgId)
   655                 item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra);
   685                 item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra);
   656             item.isPlural = true;
   686             item.isPlural = true;
   657         } else {
   687         } else {
   658             cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n"))
   688             cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'"))
   659                 .arg(l + 1).arg(codec->toUnicode(lines[l])));
   689                 .arg(l + 1).arg(codec->toUnicode(lines[l])));
   660             error = true;
   690             error = true;
   661         }
   691         }
   662     }
   692     }
   663     return !error && cd.errors().isEmpty();
   693     return !error && cd.errors().isEmpty();
   670     if (!hdrOrder.contains(qName))
   700     if (!hdrOrder.contains(qName))
   671         hdrOrder << qName;
   701         hdrOrder << qName;
   672     headers[makePoHeader(qName)] = value;
   702     headers[makePoHeader(qName)] = value;
   673 }
   703 }
   674 
   704 
       
   705 static QString escapeComment(const QString &in, bool escape)
       
   706 {
       
   707     QString out = in;
       
   708     if (escape) {
       
   709         out.replace(QLatin1Char('~'), QLatin1String("~~"));
       
   710         out.replace(QLatin1Char('|'), QLatin1String("~|"));
       
   711     }
       
   712     return out;
       
   713 }
       
   714 
   675 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd)
   715 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd)
   676 {
   716 {
   677     QString str_format = QLatin1String("-format");
   717     QString str_format = QLatin1String("-format");
   678 
   718 
   679     bool ok = true;
   719     bool ok = true;
   680     QTextStream out(&dev);
   720     QTextStream out(&dev);
   681     out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec);
   721     out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec);
       
   722 
       
   723     bool qtContexts = false;
       
   724     foreach (const TranslatorMessage &msg, translator.messages())
       
   725         if (!msg.context().isEmpty()) {
       
   726             qtContexts = true;
       
   727             break;
       
   728         }
   682 
   729 
   683     QString cmt = translator.extra(QLatin1String("po-header_comment"));
   730     QString cmt = translator.extra(QLatin1String("po-header_comment"));
   684     if (!cmt.isEmpty())
   731     if (!cmt.isEmpty())
   685         out << cmt << '\n';
   732         out << cmt << '\n';
   686     out << "msgid \"\"\n";
   733     out << "msgid \"\"\n";
   701             addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules));
   748             addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules));
   702         addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode());
   749         addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode());
   703     }
   750     }
   704     if (!translator.sourceLanguageCode().isEmpty())
   751     if (!translator.sourceLanguageCode().isEmpty())
   705         addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode());
   752         addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode());
       
   753     if (qtContexts)
       
   754         addPoHeader(headers, hdrOrder, "X-Qt-Contexts", QLatin1String("true"));
   706     QString hdrStr;
   755     QString hdrStr;
   707     foreach (const QString &hdr, hdrOrder) {
   756     foreach (const QString &hdr, hdrOrder) {
   708         hdrStr += hdr;
   757         hdrStr += hdr;
   709         hdrStr += QLatin1String(": ");
   758         hdrStr += QLatin1String(": ");
   710         hdrStr += headers.value(makePoHeader(hdr));
   759         hdrStr += headers.value(makePoHeader(hdr));
   719             out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment());
   768             out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment());
   720 
   769 
   721         if (!msg.extraComment().isEmpty())
   770         if (!msg.extraComment().isEmpty())
   722             out << poEscapedLines(QLatin1String("#."), true, msg.extraComment());
   771             out << poEscapedLines(QLatin1String("#."), true, msg.extraComment());
   723 
   772 
   724         if (!msg.context().isEmpty())
       
   725             out << QLatin1String("#. ts-context ") << msg.context() << '\n';
       
   726         if (!msg.id().isEmpty())
   773         if (!msg.id().isEmpty())
   727             out << QLatin1String("#. ts-id ") << msg.id() << '\n';
   774             out << QLatin1String("#. ts-id ") << msg.id() << '\n';
   728 
   775 
   729         if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) {
   776         if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) {
   730             QStringList refs;
   777             QStringList refs;
   768         if (!flags.isEmpty())
   815         if (!flags.isEmpty())
   769             out << "#, " << flags.join(QLatin1String(", ")) << '\n';
   816             out << "#, " << flags.join(QLatin1String(", ")) << '\n';
   770 
   817 
   771         QString prefix = QLatin1String("#| ");
   818         QString prefix = QLatin1String("#| ");
   772         if (!msg.oldComment().isEmpty())
   819         if (!msg.oldComment().isEmpty())
   773             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.oldComment());
   820             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
       
   821                                    escapeComment(msg.oldComment(), qtContexts));
   774         if (!msg.oldSourceText().isEmpty())
   822         if (!msg.oldSourceText().isEmpty())
   775             out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText());
   823             out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText());
   776         QString plural = msg.extra(QLatin1String("po-old_msgid_plural"));
   824         QString plural = msg.extra(QLatin1String("po-old_msgid_plural"));
   777         if (!plural.isEmpty())
   825         if (!plural.isEmpty())
   778             out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
   826             out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
   779         prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : "");
   827         prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : "");
   780         if (!msg.comment().isEmpty())
   828         if (!msg.context().isEmpty())
   781             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.comment());
   829             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
       
   830                                    escapeComment(msg.context(), true) + QLatin1Char('|')
       
   831                                    + escapeComment(msg.comment(), true));
       
   832         else if (!msg.comment().isEmpty())
       
   833             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
       
   834                                    escapeComment(msg.comment(), qtContexts));
   782         out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText());
   835         out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText());
   783         if (!msg.isPlural()) {
   836         if (!msg.isPlural()) {
   784             QString transl = msg.translation();
   837             QString transl = msg.translation();
   785             transl.replace(QChar(Translator::BinaryVariantSeparator),
   838             transl.replace(QChar(Translator::BinaryVariantSeparator),
   786                            QChar(Translator::TextVariantSeparator));
   839                            QChar(Translator::TextVariantSeparator));