${CMAKE_SOURCE_DIR}/src/shared/linguist/numerus.cpp
${CMAKE_SOURCE_DIR}/src/shared/linguist/translator.cpp
${CMAKE_SOURCE_DIR}/src/shared/linguist/translatormessage.cpp
- ${CMAKE_SOURCE_DIR}/src/shared/linguist/qm.cpp
- ${CMAKE_SOURCE_DIR}/src/shared/linguist/qph.cpp
- ${CMAKE_SOURCE_DIR}/src/shared/linguist/po.cpp
- ${CMAKE_SOURCE_DIR}/src/shared/linguist/ts.cpp
)
set(SHAREDLINGUIST_HEADERS
+++ /dev/null
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Qt Linguist of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** As a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "translator.h"
-
-#include <QtCore/QDebug>
-#include <QtCore/QIODevice>
-#include <QtCore/QHash>
-#include <QtCore/QString>
-#include <QtCore/QTextCodec>
-#include <QtCore/QTextStream>
-
-#include <ctype.h>
-
-// Uncomment if you wish to hard wrap long lines in .po files. Note that this
-// affects only msg strings, not comments.
-//#define HARD_WRAP_LONG_WORDS
-
-QT_BEGIN_NAMESPACE
-
-static const int MAX_LEN = 79;
-
-static QString poEscapedString(const QString &prefix, const QString &keyword,
- bool noWrap, const QString &ba)
-{
- QStringList lines;
- int off = 0;
- QString res;
- while (off < ba.length()) {
- ushort c = ba[off++].unicode();
- switch (c) {
- case '\n':
- res += QLatin1String("\\n");
- lines.append(res);
- res.clear();
- break;
- case '\r':
- res += QLatin1String("\\r");
- break;
- case '\t':
- res += QLatin1String("\\t");
- break;
- case '\v':
- res += QLatin1String("\\v");
- break;
- case '\a':
- res += QLatin1String("\\a");
- break;
- case '\b':
- res += QLatin1String("\\b");
- break;
- case '\f':
- res += QLatin1String("\\f");
- break;
- case '"':
- res += QLatin1String("\\\"");
- break;
- case '\\':
- res += QLatin1String("\\\\");
- break;
- default:
- if (c < 32) {
- res += QLatin1String("\\x");
- res += QString::number(c, 16);
- if (off < ba.length() && isxdigit(ba[off].unicode()))
- res += QLatin1String("\"\"");
- } else {
- res += QChar(c);
- }
- break;
- }
- }
- if (!res.isEmpty())
- lines.append(res);
- if (!lines.isEmpty()) {
- if (!noWrap) {
- if (lines.count() != 1 ||
- lines.first().length() > MAX_LEN - keyword.length() - prefix.length() - 3)
- {
- QStringList olines = lines;
- lines = QStringList(QString());
- const int maxlen = MAX_LEN - prefix.length() - 2;
- foreach (const QString &line, olines) {
- int off = 0;
- while (off + maxlen < line.length()) {
- int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1) + 1;
- if (idx == off) {
-#ifdef HARD_WRAP_LONG_WORDS
- // This doesn't seem too nice, but who knows ...
- idx = off + maxlen;
-#else
- idx = line.indexOf(QLatin1Char(' '), off + maxlen) + 1;
- if (!idx)
- break;
-#endif
- }
- lines.append(line.mid(off, idx - off));
- off = idx;
- }
- lines.append(line.mid(off));
- }
- }
- } else if (lines.count() > 1) {
- lines.prepend(QString());
- }
- }
- return prefix + keyword + QLatin1String(" \"") +
- lines.join(QLatin1String("\"\n") + prefix + QLatin1Char('"')) +
- QLatin1String("\"\n");
-}
-
-static QString poEscapedLines(const QString &prefix, bool addSpace, const QStringList &lines)
-{
- QString out;
- foreach (const QString &line, lines) {
- out += prefix;
- if (addSpace && !line.isEmpty())
- out += QLatin1Char(' ' );
- out += line;
- out += QLatin1Char('\n');
- }
- return out;
-}
-
-static QString poEscapedLines(const QString &prefix, bool addSpace, const QString &in0)
-{
- QString in = in0;
- if (in.endsWith(QLatin1Char('\n')))
- in.chop(1);
- return poEscapedLines(prefix, addSpace, in.split(QLatin1Char('\n')));
-}
-
-static QString poWrappedEscapedLines(const QString &prefix, bool addSpace, const QString &line)
-{
- const int maxlen = MAX_LEN - prefix.length();
- QStringList lines;
- int off = 0;
- while (off + maxlen < line.length()) {
- int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1);
- if (idx < off) {
-#if 0 //def HARD_WRAP_LONG_WORDS
- // This cannot work without messing up semantics, so do not even try.
-#else
- idx = line.indexOf(QLatin1Char(' '), off + maxlen);
- if (idx < 0)
- break;
-#endif
- }
- lines.append(line.mid(off, idx - off));
- off = idx + 1;
- }
- lines.append(line.mid(off));
- return poEscapedLines(prefix, addSpace, lines);
-}
-
-struct PoItem
-{
-public:
- PoItem()
- : isPlural(false), isFuzzy(false)
- {}
-
-
-public:
- QByteArray id;
- QByteArray context;
- QByteArray tscomment;
- QByteArray oldTscomment;
- QByteArray lineNumber;
- QByteArray fileName;
- QByteArray references;
- QByteArray translatorComments;
- QByteArray automaticComments;
- QByteArray msgId;
- QByteArray oldMsgId;
- QList<QByteArray> msgStr;
- bool isPlural;
- bool isFuzzy;
- QHash<QString, QString> extra;
-};
-
-
-static bool isTranslationLine(const QByteArray &line)
-{
- return line.startsWith("#~ msgstr") || line.startsWith("msgstr");
-}
-
-static QByteArray slurpEscapedString(const QList<QByteArray> &lines, int &l,
- int offset, const QByteArray &prefix, ConversionData &cd)
-{
- QByteArray msg;
- int stoff;
-
- for (; l < lines.size(); ++l) {
- const QByteArray &line = lines.at(l);
- if (line.isEmpty() || !line.startsWith(prefix))
- break;
- while (isspace(line[offset])) // No length check, as string has no trailing spaces.
- offset++;
- if (line[offset] != '"')
- break;
- offset++;
- forever {
- if (offset == line.length())
- goto premature_eol;
- uchar c = line[offset++];
- if (c == '"') {
- if (offset == line.length())
- break;
- while (isspace(line[offset]))
- offset++;
- if (line[offset++] != '"') {
- cd.appendError(QString::fromLatin1(
- "PO parsing error: extra characters on line %1.")
- .arg(l + 1));
- break;
- }
- continue;
- }
- if (c == '\\') {
- if (offset == line.length())
- goto premature_eol;
- c = line[offset++];
- switch (c) {
- case 'r':
- msg += '\r'; // Maybe just throw it away?
- break;
- case 'n':
- msg += '\n';
- break;
- case 't':
- msg += '\t';
- break;
- case 'v':
- msg += '\v';
- break;
- case 'a':
- msg += '\a';
- break;
- case 'b':
- msg += '\b';
- break;
- case 'f':
- msg += '\f';
- break;
- case '"':
- msg += '"';
- break;
- case '\\':
- msg += '\\';
- break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- stoff = offset - 1;
- while ((c = line[offset]) >= '0' && c <= '7')
- if (++offset == line.length())
- goto premature_eol;
- msg += line.mid(stoff, offset - stoff).toUInt(0, 8);
- break;
- case 'x':
- stoff = offset;
- while (isxdigit(line[offset]))
- if (++offset == line.length())
- goto premature_eol;
- msg += line.mid(stoff, offset - stoff).toUInt(0, 16);
- break;
- default:
- cd.appendError(QString::fromLatin1(
- "PO parsing error: invalid escape '\\%1' (line %2).")
- .arg(QChar((uint)c)).arg(l + 1));
- msg += '\\';
- msg += c;
- break;
- }
- } else {
- msg += c;
- }
- }
- offset = prefix.size();
- }
- --l;
- return msg;
-
-premature_eol:
- cd.appendError(QString::fromLatin1(
- "PO parsing error: premature end of line %1.").arg(l + 1));
- return QByteArray();
-}
-
-static void slurpComment(QByteArray &msg, const QList<QByteArray> &lines, int & l)
-{
- QByteArray prefix = lines.at(l);
- for (int i = 1; ; i++) {
- if (prefix.at(i) != ' ') {
- prefix.truncate(i);
- break;
- }
- }
- for (; l < lines.size(); ++l) {
- const QByteArray &line = lines.at(l);
- if (line.startsWith(prefix))
- msg += line.mid(prefix.size());
- else if (line != "#")
- break;
- msg += '\n';
- }
- --l;
-}
-
-static void splitContext(QByteArray *comment, QByteArray *context)
-{
- char *data = comment->data();
- int len = comment->size();
- int sep = -1, j = 0;
-
- for (int i = 0; i < len; i++, j++) {
- if (data[i] == '~' && i + 1 < len)
- i++;
- else if (data[i] == '|')
- sep = j;
- data[j] = data[i];
- }
- if (sep >= 0) {
- QByteArray tmp = comment->mid(sep + 1, j - sep - 1);
- comment->truncate(sep);
- *context = *comment;
- *comment = tmp;
- } else {
- comment->truncate(j);
- }
-}
-
-static QString makePoHeader(const QString &str)
-{
- return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_'));
-}
-
-static QByteArray QByteArrayList_join(const QList<QByteArray> &that, char sep)
-{
- int totalLength = 0;
- const int size = that.size();
-
- for (int i = 0; i < size; ++i)
- totalLength += that.at(i).size();
-
- if (size > 0)
- totalLength += size - 1;
-
- QByteArray res;
- if (totalLength == 0)
- return res;
- res.reserve(totalLength);
- for (int i = 0; i < that.size(); ++i) {
- if (i)
- res += sep;
- res += that.at(i);
- }
- return res;
-}
-
-bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- QTextCodec *codec = QTextCodec::codecForName(
- cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource);
- bool error = false;
-
- // format of a .po file entry:
- // white-space
- // # translator-comments
- // #. automatic-comments
- // #: reference...
- // #, flag...
- // #~ msgctxt, msgid*, msgstr - used for obsoleted messages
- // #| msgctxt, msgid* previous untranslated-string - for fuzzy message
- // msgctx string-context
- // msgid untranslated-string
- // -- For singular:
- // msgstr translated-string
- // -- For plural:
- // msgid_plural untranslated-string-plural
- // msgstr[0] translated-string
- // ...
-
- // we need line based lookahead below.
- QList<QByteArray> lines;
- while (!dev.atEnd())
- lines.append(dev.readLine().trimmed());
- lines.append(QByteArray());
-
- int l = 0, lastCmtLine = -1;
- bool qtContexts = false;
- PoItem item;
- for (; l != lines.size(); ++l) {
- QByteArray line = lines.at(l);
- if (line.isEmpty())
- continue;
- if (isTranslationLine(line)) {
- bool isObsolete = line.startsWith("#~ msgstr");
- const QByteArray prefix = isObsolete ? "#~ " : "";
- while (true) {
- int idx = line.indexOf(' ', prefix.length());
- QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd);
- item.msgStr.append(str);
- if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
- break;
- ++l;
- line = lines.at(l);
- }
- if (item.msgId.isEmpty()) {
- QHash<QString, QByteArray> extras;
- QList<QByteArray> hdrOrder;
- QByteArray pluralForms;
- foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) {
- if (hdr.isEmpty())
- continue;
- int idx = hdr.indexOf(':');
- if (idx < 0) {
- cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'")
- .arg(QString::fromLatin1(hdr)));
- error = true;
- break;
- }
- QByteArray hdrName = hdr.left(idx).trimmed();
- QByteArray hdrValue = hdr.mid(idx + 1).trimmed();
- hdrOrder << hdrName;
- if (hdrName == "X-Language") {
- translator.setLanguageCode(QString::fromLatin1(hdrValue));
- } else if (hdrName == "X-Source-Language") {
- translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
- } else if (hdrName == "X-Qt-Contexts") {
- qtContexts = (hdrValue == "true");
- } else if (hdrName == "Plural-Forms") {
- pluralForms = hdrValue;
- } else if (hdrName == "MIME-Version") {
- // just assume it is 1.0
- } else if (hdrName == "Content-Type") {
- if (cd.m_codecForSource.isEmpty()) {
- if (!hdrValue.startsWith("text/plain; charset=")) {
- cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'")
- .arg(QString::fromLatin1(hdrValue)));
- error = true;
- // This will avoid a flood of conversion errors.
- codec = QTextCodec::codecForName("latin1");
- } else {
- QByteArray cod = hdrValue.mid(20);
- QTextCodec *cdc = QTextCodec::codecForName(cod);
- if (!cdc) {
- cd.appendError(QString::fromLatin1("Unsupported codec '%1'")
- .arg(QString::fromLatin1(cod)));
- error = true;
- // This will avoid a flood of conversion errors.
- codec = QTextCodec::codecForName("latin1");
- } else {
- codec = cdc;
- }
- }
- }
- } else if (hdrName == "Content-Transfer-Encoding") {
- if (hdrValue != "8bit") {
- cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'")
- .arg(QString::fromLatin1(hdrValue)));
- return false;
- }
- } else if (hdrName == "X-Virgin-Header") {
- // legacy
- } else {
- extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue;
- }
- }
- if (!pluralForms.isEmpty()) {
- if (translator.languageCode().isEmpty()) {
- extras[makePoHeader(QLatin1String("Plural-Forms"))] = pluralForms;
- } else {
- // FIXME: have fun with making a consistency check ...
- }
- }
- // Eliminate the field if only headers we added are present in standard order.
- // Keep in sync with savePO
- static const char * const dfltHdrs[] = {
- "MIME-Version", "Content-Type", "Content-Transfer-Encoding",
- "Plural-Forms", "X-Language", "X-Source-Language", "X-Qt-Contexts"
- };
- uint cdh = 0;
- for (int cho = 0; cho < hdrOrder.length(); cho++) {
- for (;; cdh++) {
- if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) {
- extras[QLatin1String("po-headers")] =
- QByteArrayList_join(hdrOrder, ',');
- goto doneho;
- }
- if (hdrOrder.at(cho) == dfltHdrs[cdh]) {
- cdh++;
- break;
- }
- }
- }
- doneho:
- if (lastCmtLine != -1)
- extras[QLatin1String("po-header_comment")] =
- QByteArrayList_join(lines.mid(0, lastCmtLine + 1), '\n');
- for (QHash<QString, QByteArray>::ConstIterator it = extras.constBegin(),
- end = extras.constEnd();
- it != end; ++it)
- translator.setExtra(it.key(), codec->toUnicode(it.value()));
- item = PoItem();
- continue;
- }
- // build translator message
- TranslatorMessage msg;
- msg.setContext(codec->toUnicode(item.context));
- if (!item.references.isEmpty()) {
- QString xrefs;
- foreach (const QString &ref,
- codec->toUnicode(item.references).split(
- QRegExp(QLatin1String("\\s")), QString::SkipEmptyParts)) {
- int pos = ref.indexOf(QLatin1Char(':'));
- int lpos = ref.lastIndexOf(QLatin1Char(':'));
- if (pos != -1 && pos == lpos) {
- bool ok;
- int lno = ref.mid(pos + 1).toInt(&ok);
- if (ok) {
- msg.addReference(ref.left(pos), lno);
- continue;
- }
- }
- if (!xrefs.isEmpty())
- xrefs += QLatin1Char(' ');
- xrefs += ref;
- }
- if (!xrefs.isEmpty())
- item.extra[QLatin1String("po-references")] = xrefs;
- }
- msg.setId(codec->toUnicode(item.id));
- msg.setSourceText(codec->toUnicode(item.msgId));
- msg.setOldSourceText(codec->toUnicode(item.oldMsgId));
- msg.setComment(codec->toUnicode(item.tscomment));
- msg.setOldComment(codec->toUnicode(item.oldTscomment));
- msg.setExtraComment(codec->toUnicode(item.automaticComments));
- msg.setTranslatorComment(codec->toUnicode(item.translatorComments));
- msg.setPlural(item.isPlural || item.msgStr.size() > 1);
- QStringList translations;
- foreach (const QByteArray &bstr, item.msgStr) {
- QString str = codec->toUnicode(bstr);
- str.replace(QChar(Translator::TextVariantSeparator),
- QChar(Translator::BinaryVariantSeparator));
- translations << str;
- }
- msg.setTranslations(translations);
- if (isObsolete)
- msg.setType(TranslatorMessage::Obsolete);
- else if (item.isFuzzy || (!msg.sourceText().isEmpty() && !msg.isTranslated()))
- msg.setType(TranslatorMessage::Unfinished);
- else
- msg.setType(TranslatorMessage::Finished);
- msg.setExtras(item.extra);
-
- //qDebug() << "WRITE: " << context;
- //qDebug() << "SOURCE: " << msg.sourceText();
- //qDebug() << flags << msg.m_extra;
- translator.append(msg);
- item = PoItem();
- } else if (line.startsWith('#')) {
- switch (line.size() < 2 ? 0 : line.at(1)) {
- case ':':
- item.references += line.mid(3);
- item.references += '\n';
- break;
- case ',': {
- QStringList flags =
- QString::fromLatin1(line.mid(2)).split(
- QRegExp(QLatin1String("[, ]")), QString::SkipEmptyParts);
- if (flags.removeOne(QLatin1String("fuzzy")))
- item.isFuzzy = true;
- flags.removeOne(QLatin1String("qt-format"));
- TranslatorMessage::ExtraData::const_iterator it =
- item.extra.constFind(QLatin1String("po-flags"));
- if (it != item.extra.constEnd())
- flags.prepend(*it);
- if (!flags.isEmpty())
- item.extra[QLatin1String("po-flags")] = flags.join(QLatin1String(", "));
- break;
- }
- case 0:
- item.translatorComments += '\n';
- break;
- case ' ':
- slurpComment(item.translatorComments, lines, l);
- break;
- case '.':
- if (line.startsWith("#. ts-context ")) { // legacy
- item.context = line.mid(14);
- } else if (line.startsWith("#. ts-id ")) {
- item.id = line.mid(9);
- } else {
- item.automaticComments += line.mid(3);
- item.automaticComments += '\n';
- }
- break;
- case '|':
- if (line.startsWith("#| msgid ")) {
- item.oldMsgId = slurpEscapedString(lines, l, 9, "#| ", cd);
- } else if (line.startsWith("#| msgid_plural ")) {
- QByteArray extra = slurpEscapedString(lines, l, 16, "#| ", cd);
- if (extra != item.oldMsgId)
- item.extra[QLatin1String("po-old_msgid_plural")] =
- codec->toUnicode(extra);
- } else if (line.startsWith("#| msgctxt ")) {
- item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd);
- if (qtContexts)
- splitContext(&item.oldTscomment, &item.context);
- } else {
- cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
- .arg(l + 1).arg(codec->toUnicode(lines[l])));
- error = true;
- }
- break;
- case '~':
- if (line.startsWith("#~ msgid ")) {
- item.msgId = slurpEscapedString(lines, l, 9, "#~ ", cd);
- } else if (line.startsWith("#~ msgid_plural ")) {
- QByteArray extra = slurpEscapedString(lines, l, 16, "#~ ", cd);
- if (extra != item.msgId)
- item.extra[QLatin1String("po-msgid_plural")] =
- codec->toUnicode(extra);
- item.isPlural = true;
- } else if (line.startsWith("#~ msgctxt ")) {
- item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd);
- if (qtContexts)
- splitContext(&item.tscomment, &item.context);
- } else {
- cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
- .arg(l + 1).arg(codec->toUnicode(lines[l])));
- error = true;
- }
- break;
- default:
- cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'"))
- .arg(l + 1).arg(codec->toUnicode(lines[l])));
- error = true;
- break;
- }
- lastCmtLine = l;
- } else if (line.startsWith("msgctxt ")) {
- item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
- if (qtContexts)
- splitContext(&item.tscomment, &item.context);
- } else if (line.startsWith("msgid ")) {
- item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
- } else if (line.startsWith("msgid_plural ")) {
- QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
- if (extra != item.msgId)
- item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra);
- item.isPlural = true;
- } else {
- cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'"))
- .arg(l + 1).arg(codec->toUnicode(lines[l])));
- error = true;
- }
- }
- return !error && cd.errors().isEmpty();
-}
-
-static void addPoHeader(Translator::ExtraData &headers, QStringList &hdrOrder,
- const char *name, const QString &value)
-{
- QString qName = QLatin1String(name);
- if (!hdrOrder.contains(qName))
- hdrOrder << qName;
- headers[makePoHeader(qName)] = value;
-}
-
-static QString escapeComment(const QString &in, bool escape)
-{
- QString out = in;
- if (escape) {
- out.replace(QLatin1Char('~'), QLatin1String("~~"));
- out.replace(QLatin1Char('|'), QLatin1String("~|"));
- }
- return out;
-}
-
-bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- QString str_format = QLatin1String("-format");
-
- bool ok = true;
- QTextStream out(&dev);
- out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec);
-
- bool qtContexts = false;
- foreach (const TranslatorMessage &msg, translator.messages())
- if (!msg.context().isEmpty()) {
- qtContexts = true;
- break;
- }
-
- QString cmt = translator.extra(QLatin1String("po-header_comment"));
- if (!cmt.isEmpty())
- out << cmt << '\n';
- out << "msgid \"\"\n";
- Translator::ExtraData headers = translator.extras();
- QStringList hdrOrder = translator.extra(QLatin1String("po-headers")).split(
- QLatin1Char(','), QString::SkipEmptyParts);
- // Keep in sync with loadPO
- addPoHeader(headers, hdrOrder, "MIME-Version", QLatin1String("1.0"));
- addPoHeader(headers, hdrOrder, "Content-Type",
- QLatin1String("text/plain; charset=" + out.codec()->name()));
- addPoHeader(headers, hdrOrder, "Content-Transfer-Encoding", QLatin1String("8bit"));
- if (!translator.languageCode().isEmpty()) {
- QLocale::Language l;
- QLocale::Country c;
- Translator::languageAndCountry(translator.languageCode(), &l, &c);
- const char *gettextRules;
- if (getNumerusInfo(l, c, 0, 0, &gettextRules))
- addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules));
- addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode());
- }
- if (!translator.sourceLanguageCode().isEmpty())
- addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode());
- if (qtContexts)
- addPoHeader(headers, hdrOrder, "X-Qt-Contexts", QLatin1String("true"));
- QString hdrStr;
- foreach (const QString &hdr, hdrOrder) {
- hdrStr += hdr;
- hdrStr += QLatin1String(": ");
- hdrStr += headers.value(makePoHeader(hdr));
- hdrStr += QLatin1Char('\n');
- }
- out << poEscapedString(QString(), QString::fromLatin1("msgstr"), true, hdrStr);
-
- foreach (const TranslatorMessage &msg, translator.messages()) {
- out << endl;
-
- if (!msg.translatorComment().isEmpty())
- out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment());
-
- if (!msg.extraComment().isEmpty())
- out << poEscapedLines(QLatin1String("#."), true, msg.extraComment());
-
- if (!msg.id().isEmpty())
- out << QLatin1String("#. ts-id ") << msg.id() << '\n';
-
- QString xrefs = msg.extra(QLatin1String("po-references"));
- if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) {
- QStringList refs;
- foreach (const TranslatorMessage::Reference &ref, msg.allReferences())
- refs.append(QString(QLatin1String("%2:%1"))
- .arg(ref.lineNumber()).arg(ref.fileName()));
- if (!xrefs.isEmpty())
- refs << xrefs;
- out << poWrappedEscapedLines(QLatin1String("#:"), true, refs.join(QLatin1String(" ")));
- }
-
- bool noWrap = false;
- bool skipFormat = false;
- QStringList flags;
- if (msg.type() == TranslatorMessage::Unfinished && msg.isTranslated())
- flags.append(QLatin1String("fuzzy"));
- TranslatorMessage::ExtraData::const_iterator itr =
- msg.extras().constFind(QLatin1String("po-flags"));
- if (itr != msg.extras().constEnd()) {
- QStringList atoms = itr->split(QLatin1String(", "));
- foreach (const QString &atom, atoms)
- if (atom.endsWith(str_format)) {
- skipFormat = true;
- break;
- }
- if (atoms.contains(QLatin1String("no-wrap")))
- noWrap = true;
- flags.append(*itr);
- }
- if (!skipFormat) {
- QString source = msg.sourceText();
- // This is fuzzy logic, as we don't know whether the string is
- // actually used with QString::arg().
- for (int off = 0; (off = source.indexOf(QLatin1Char('%'), off)) >= 0; ) {
- if (++off >= source.length())
- break;
- if (source.at(off) == QLatin1Char('n') || source.at(off).isDigit()) {
- flags.append(QLatin1String("qt-format"));
- break;
- }
- }
- }
- if (!flags.isEmpty())
- out << "#, " << flags.join(QLatin1String(", ")) << '\n';
-
- QString prefix = QLatin1String("#| ");
- if (!msg.oldComment().isEmpty())
- out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
- escapeComment(msg.oldComment(), qtContexts));
- if (!msg.oldSourceText().isEmpty())
- out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText());
- QString plural = msg.extra(QLatin1String("po-old_msgid_plural"));
- if (!plural.isEmpty())
- out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
- prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : "");
- if (!msg.context().isEmpty())
- out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
- escapeComment(msg.context(), true) + QLatin1Char('|')
- + escapeComment(msg.comment(), true));
- else if (!msg.comment().isEmpty())
- out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap,
- escapeComment(msg.comment(), qtContexts));
- out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText());
- if (!msg.isPlural()) {
- QString transl = msg.translation();
- transl.replace(QChar(Translator::BinaryVariantSeparator),
- QChar(Translator::TextVariantSeparator));
- out << poEscapedString(prefix, QLatin1String("msgstr"), noWrap, transl);
- } else {
- QString plural = msg.extra(QLatin1String("po-msgid_plural"));
- if (plural.isEmpty())
- plural = msg.sourceText();
- out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
- const QStringList &translations = msg.translations();
- for (int i = 0; i != translations.size(); ++i) {
- QString str = translations.at(i);
- str.replace(QChar(Translator::BinaryVariantSeparator),
- QChar(Translator::TextVariantSeparator));
- out << poEscapedString(prefix, QString::fromLatin1("msgstr[%1]").arg(i), noWrap,
- str);
- }
- }
- }
- return ok;
-}
-
-static bool savePOT(const Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- Translator ttor = translator;
- ttor.dropTranslations();
- return savePO(ttor, dev, cd);
-}
-
-int initPO()
-{
- Translator::FileFormat format;
- format.extension = QLatin1String("po");
- format.description = QObject::tr("GNU Gettext localization files");
- format.loader = &loadPO;
- format.saver = &savePO;
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = 1;
- Translator::registerFileFormat(format);
- format.extension = QLatin1String("pot");
- format.description = QObject::tr("GNU Gettext localization template files");
- format.loader = &loadPO;
- format.saver = &savePOT;
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = -1;
- Translator::registerFileFormat(format);
- return 1;
-}
-
-Q_CONSTRUCTOR_FUNCTION(initPO)
-
-QT_END_NAMESPACE
+++ /dev/null
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Qt Linguist of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** As a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "translator.h"
-
-#ifndef QT_BOOTSTRAPPED
-#include <QtCore/QCoreApplication>
-#endif
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMap>
-#include <QtCore/QString>
-#include <QtCore/QTextCodec>
-
-QT_BEGIN_NAMESPACE
-
-// magic number for the file
-static const int MagicLength = 16;
-static const uchar magic[MagicLength] = {
- 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
- 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
-};
-
-
-namespace {
-
-enum Tag {
- Tag_End = 1,
- Tag_SourceText16 = 2,
- Tag_Translation = 3,
- Tag_Context16 = 4,
- Tag_Obsolete1 = 5,
- Tag_SourceText = 6,
- Tag_Context = 7,
- Tag_Comment = 8,
- Tag_Obsolete2 = 9
-};
-
-enum Prefix {
- NoPrefix,
- Hash,
- HashContext,
- HashContextSourceText,
- HashContextSourceTextComment
-};
-
-} // namespace anon
-
-static uint elfHash(const QByteArray &ba)
-{
- const uchar *k = (const uchar *)ba.data();
- uint h = 0;
- uint g;
-
- if (k) {
- while (*k) {
- h = (h << 4) + *k++;
- if ((g = (h & 0xf0000000)) != 0)
- h ^= g >> 24;
- h &= ~g;
- }
- }
- if (!h)
- h = 1;
- return h;
-}
-
-class ByteTranslatorMessage
-{
-public:
- ByteTranslatorMessage(
- const QByteArray &context,
- const QByteArray &sourceText,
- const QByteArray &comment,
- const QStringList &translations) :
- m_context(context),
- m_sourcetext(sourceText),
- m_comment(comment),
- m_translations(translations)
- {}
- const QByteArray &context() const { return m_context; }
- const QByteArray &sourceText() const { return m_sourcetext; }
- const QByteArray &comment() const { return m_comment; }
- const QStringList &translations() const { return m_translations; }
- bool operator<(const ByteTranslatorMessage& m) const;
-
-private:
- QByteArray m_context;
- QByteArray m_sourcetext;
- QByteArray m_comment;
- QStringList m_translations;
-};
-
-Q_DECLARE_TYPEINFO(ByteTranslatorMessage, Q_MOVABLE_TYPE);
-
-bool ByteTranslatorMessage::operator<(const ByteTranslatorMessage& m) const
-{
- if (m_context != m.m_context)
- return m_context < m.m_context;
- if (m_sourcetext != m.m_sourcetext)
- return m_sourcetext < m.m_sourcetext;
- return m_comment < m.m_comment;
-}
-
-class Releaser
-{
-public:
- struct Offset {
- Offset()
- : h(0), o(0)
- {}
- Offset(uint hash, uint offset)
- : h(hash), o(offset)
- {}
-
- bool operator<(const Offset &other) const {
- return (h != other.h) ? h < other.h : o < other.o;
- }
- bool operator==(const Offset &other) const {
- return h == other.h && o == other.o;
- }
- uint h;
- uint o;
- };
-
- enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
-
- Releaser() : m_codec(0) {}
-
- void setCodecName(const QByteArray &codecName)
- {
- m_codec = QTextCodec::codecForName(codecName);
- }
-
- bool save(QIODevice *iod);
-
- void insert(const TranslatorMessage &msg, const QStringList &tlns, bool forceComment);
- void insertIdBased(const TranslatorMessage &message, const QStringList &tlns);
-
- void squeeze(TranslatorSaveMode mode);
-
- void setNumerusRules(const QByteArray &rules);
-
-private:
- Q_DISABLE_COPY(Releaser)
-
- // This should reproduce the byte array fetched from the source file, which
- // on turn should be the same as passed to the actual tr(...) calls
- QByteArray originalBytes(const QString &str, bool isUtf8) const;
-
- void insertInternal(const TranslatorMessage &message, const QStringList &tlns,
- bool forceComment, bool isUtf8);
-
- static Prefix commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2);
-
- static uint msgHash(const ByteTranslatorMessage &msg);
-
- void writeMessage(const ByteTranslatorMessage & msg, QDataStream & stream,
- TranslatorSaveMode strip, Prefix prefix) const;
-
- // for squeezed but non-file data, this is what needs to be deleted
- QByteArray m_messageArray;
- QByteArray m_offsetArray;
- QByteArray m_contextArray;
- QMap<ByteTranslatorMessage, void *> m_messages;
- QByteArray m_numerusRules;
-
- // Used to reproduce the original bytes
- QTextCodec *m_codec;
-};
-
-QByteArray Releaser::originalBytes(const QString &str, bool isUtf8) const
-{
- if (str.isEmpty()) {
- // Do not use QByteArray() here as the result of the serialization
- // will be different.
- return QByteArray("");
- }
- if (isUtf8)
- return str.toUtf8();
- return m_codec ? m_codec->fromUnicode(str) : str.toLatin1();
-}
-
-uint Releaser::msgHash(const ByteTranslatorMessage &msg)
-{
- return elfHash(msg.sourceText() + msg.comment());
-}
-
-Prefix Releaser::commonPrefix(const ByteTranslatorMessage &m1, const ByteTranslatorMessage &m2)
-{
- if (msgHash(m1) != msgHash(m2))
- return NoPrefix;
- if (m1.context() != m2.context())
- return Hash;
- if (m1.sourceText() != m2.sourceText())
- return HashContext;
- if (m1.comment() != m2.comment())
- return HashContextSourceText;
- return HashContextSourceTextComment;
-}
-
-void Releaser::writeMessage(const ByteTranslatorMessage &msg, QDataStream &stream,
- TranslatorSaveMode mode, Prefix prefix) const
-{
- for (int i = 0; i < msg.translations().count(); ++i)
- stream << quint8(Tag_Translation) << msg.translations().at(i);
-
- if (mode == SaveEverything)
- prefix = HashContextSourceTextComment;
-
- // lrelease produces "wrong" QM files for QByteArrays that are .isNull().
- switch (prefix) {
- default:
- case HashContextSourceTextComment:
- stream << quint8(Tag_Comment) << msg.comment();
- // fall through
- case HashContextSourceText:
- stream << quint8(Tag_SourceText) << msg.sourceText();
- // fall through
- case HashContext:
- stream << quint8(Tag_Context) << msg.context();
- break;
- }
-
- stream << quint8(Tag_End);
-}
-
-
-bool Releaser::save(QIODevice *iod)
-{
- QDataStream s(iod);
- s.writeRawData((const char *)magic, MagicLength);
-
- if (!m_offsetArray.isEmpty()) {
- quint32 oas = quint32(m_offsetArray.size());
- s << quint8(Hashes) << oas;
- s.writeRawData(m_offsetArray.constData(), oas);
- }
- if (!m_messageArray.isEmpty()) {
- quint32 mas = quint32(m_messageArray.size());
- s << quint8(Messages) << mas;
- s.writeRawData(m_messageArray.constData(), mas);
- }
- if (!m_contextArray.isEmpty()) {
- quint32 cas = quint32(m_contextArray.size());
- s << quint8(Contexts) << cas;
- s.writeRawData(m_contextArray.constData(), cas);
- }
- if (!m_numerusRules.isEmpty()) {
- quint32 nrs = m_numerusRules.size();
- s << quint8(NumerusRules) << nrs;
- s.writeRawData(m_numerusRules.constData(), nrs);
- }
- return true;
-}
-
-void Releaser::squeeze(TranslatorSaveMode mode)
-{
- if (m_messages.isEmpty() && mode == SaveEverything)
- return;
-
- QMap<ByteTranslatorMessage, void *> messages = m_messages;
-
- // re-build contents
- m_messageArray.clear();
- m_offsetArray.clear();
- m_contextArray.clear();
- m_messages.clear();
-
- QMap<Offset, void *> offsets;
-
- QDataStream ms(&m_messageArray, QIODevice::WriteOnly);
- QMap<ByteTranslatorMessage, void *>::const_iterator it, next;
- int cpPrev = 0, cpNext = 0;
- for (it = messages.constBegin(); it != messages.constEnd(); ++it) {
- cpPrev = cpNext;
- next = it;
- ++next;
- if (next == messages.constEnd())
- cpNext = 0;
- else
- cpNext = commonPrefix(it.key(), next.key());
- offsets.insert(Offset(msgHash(it.key()), ms.device()->pos()), (void *)0);
- writeMessage(it.key(), ms, mode, Prefix(qMax(cpPrev, cpNext + 1)));
- }
-
- QMap<Offset, void *>::Iterator offset;
- offset = offsets.begin();
- QDataStream ds(&m_offsetArray, QIODevice::WriteOnly);
- while (offset != offsets.end()) {
- Offset k = offset.key();
- ++offset;
- ds << quint32(k.h) << quint32(k.o);
- }
-
- if (mode == SaveStripped) {
- QMap<QByteArray, int> contextSet;
- for (it = messages.constBegin(); it != messages.constEnd(); ++it)
- ++contextSet[it.key().context()];
-
- quint16 hTableSize;
- if (contextSet.size() < 200)
- hTableSize = (contextSet.size() < 60) ? 151 : 503;
- else if (contextSet.size() < 2500)
- hTableSize = (contextSet.size() < 750) ? 1511 : 5003;
- else
- hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2;
-
- QMultiMap<int, QByteArray> hashMap;
- QMap<QByteArray, int>::const_iterator c;
- for (c = contextSet.constBegin(); c != contextSet.constEnd(); ++c)
- hashMap.insert(elfHash(c.key()) % hTableSize, c.key());
-
- /*
- The contexts found in this translator are stored in a hash
- table to provide fast lookup. The context array has the
- following format:
-
- quint16 hTableSize;
- quint16 hTable[hTableSize];
- quint8 contextPool[...];
-
- The context pool stores the contexts as Pascal strings:
-
- quint8 len;
- quint8 data[len];
-
- Let's consider the look-up of context "FunnyDialog". A
- hash value between 0 and hTableSize - 1 is computed, say h.
- If hTable[h] is 0, "FunnyDialog" is not covered by this
- translator. Else, we check in the contextPool at offset
- 2 * hTable[h] to see if "FunnyDialog" is one of the
- contexts stored there, until we find it or we meet the
- empty string.
- */
- m_contextArray.resize(2 + (hTableSize << 1));
- QDataStream t(&m_contextArray, QIODevice::WriteOnly);
-
- quint16 *hTable = new quint16[hTableSize];
- memset(hTable, 0, hTableSize * sizeof(quint16));
-
- t << hTableSize;
- t.device()->seek(2 + (hTableSize << 1));
- t << quint16(0); // the entry at offset 0 cannot be used
- uint upto = 2;
-
- QMap<int, QByteArray>::const_iterator entry = hashMap.constBegin();
- while (entry != hashMap.constEnd()) {
- int i = entry.key();
- hTable[i] = quint16(upto >> 1);
-
- do {
- const char *con = entry.value().constData();
- uint len = uint(entry.value().length());
- len = qMin(len, 255u);
- t << quint8(len);
- t.writeRawData(con, len);
- upto += 1 + len;
- ++entry;
- } while (entry != hashMap.constEnd() && entry.key() == i);
- if (upto & 0x1) {
- // offsets have to be even
- t << quint8(0); // empty string
- ++upto;
- }
- }
- t.device()->seek(2);
- for (int j = 0; j < hTableSize; j++)
- t << hTable[j];
- delete [] hTable;
-
- if (upto > 131072) {
- qWarning("Releaser::squeeze: Too many contexts");
- m_contextArray.clear();
- }
- }
-}
-
-void Releaser::insertInternal(const TranslatorMessage &message, const QStringList &tlns,
- bool forceComment, bool isUtf8)
-{
- ByteTranslatorMessage bmsg(originalBytes(message.context(), isUtf8),
- originalBytes(message.sourceText(), isUtf8),
- originalBytes(message.comment(), isUtf8),
- tlns);
- if (!forceComment) {
- ByteTranslatorMessage bmsg2(
- bmsg.context(), bmsg.sourceText(), QByteArray(""), bmsg.translations());
- if (!m_messages.contains(bmsg2)) {
- m_messages.insert(bmsg2, 0);
- return;
- }
- }
- m_messages.insert(bmsg, 0);
-}
-
-void Releaser::insert(const TranslatorMessage &message, const QStringList &tlns, bool forceComment)
-{
- insertInternal(message, tlns, forceComment, message.isUtf8());
- if (message.isUtf8() && message.isNonUtf8())
- insertInternal(message, tlns, forceComment, false);
-}
-
-void Releaser::insertIdBased(const TranslatorMessage &message, const QStringList &tlns)
-{
- ByteTranslatorMessage bmsg("", originalBytes(message.id(), false), "", tlns);
- m_messages.insert(bmsg, 0);
-}
-
-void Releaser::setNumerusRules(const QByteArray &rules)
-{
- m_numerusRules = rules;
-}
-
-static quint8 read8(const uchar *data)
-{
- return *data;
-}
-
-static quint32 read32(const uchar *data)
-{
- return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]);
-}
-
-static void fromBytes(const char *str, int len, QTextCodec *codec, QTextCodec *utf8Codec,
- QString *out, QString *utf8Out,
- bool *isSystem, bool *isUtf8, bool *needs8Bit)
-{
- for (int i = 0; i < len; ++i)
- if (str[i] & 0x80) {
- if (utf8Codec) {
- QTextCodec::ConverterState cvtState;
- *utf8Out = utf8Codec->toUnicode(str, len, &cvtState);
- *isUtf8 = !cvtState.invalidChars;
- }
- QTextCodec::ConverterState cvtState;
- *out = codec->toUnicode(str, len, &cvtState);
- *isSystem = !cvtState.invalidChars;
- *needs8Bit = true;
- return;
- }
- *out = QString::fromLatin1(str, len);
- *isSystem = true;
- if (utf8Codec) {
- *utf8Out = *out;
- *isUtf8 = true;
- }
- *needs8Bit = false;
-}
-
-bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- QByteArray ba = dev.readAll();
- const uchar *data = (uchar*)ba.data();
- int len = ba.size();
- if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) {
- cd.appendError(QLatin1String("QM-Format error: magic marker missing"));
- return false;
- }
-
- enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 };
-
- // for squeezed but non-file data, this is what needs to be deleted
- const uchar *messageArray = 0;
- const uchar *offsetArray = 0;
- uint offsetLength = 0;
-
- bool ok = true;
- const uchar *end = data + len;
-
- data += MagicLength;
-
- while (data < end - 4) {
- quint8 tag = read8(data++);
- quint32 blockLen = read32(data);
- //qDebug() << "TAG:" << tag << "BLOCKLEN:" << blockLen;
- data += 4;
- if (!tag || !blockLen)
- break;
- if (data + blockLen > end) {
- ok = false;
- break;
- }
-
- if (tag == Hashes) {
- offsetArray = data;
- offsetLength = blockLen;
- //qDebug() << "HASHES: " << blockLen << QByteArray((const char *)data, blockLen).toHex();
- } else if (tag == Messages) {
- messageArray = data;
- //qDebug() << "MESSAGES: " << blockLen << QByteArray((const char *)data, blockLen).toHex();
- }
-
- data += blockLen;
- }
-
-
- size_t numItems = offsetLength / (2 * sizeof(quint32));
- //qDebug() << "NUMITEMS: " << numItems;
-
- QTextCodec *codec = QTextCodec::codecForName(
- cd.m_codecForSource.isEmpty() ? QByteArray("Latin1") : cd.m_codecForSource);
- QTextCodec *utf8Codec = 0;
- if (codec->name() != "UTF-8")
- utf8Codec = QTextCodec::codecForName("UTF-8");
-
- QString strProN = QLatin1String("%n");
- QLocale::Language l;
- QLocale::Country c;
- Translator::languageAndCountry(translator.languageCode(), &l, &c);
- QStringList numerusForms;
- bool guessPlurals = true;
- if (getNumerusInfo(l, c, 0, &numerusForms, 0))
- guessPlurals = (numerusForms.count() == 1);
-
- QString context, contextUtf8;
- bool contextIsSystem, contextIsUtf8, contextNeeds8Bit;
- QString sourcetext, sourcetextUtf8;
- bool sourcetextIsSystem, sourcetextIsUtf8, sourcetextNeeds8Bit;
- QString comment, commentUtf8;
- bool commentIsSystem, commentIsUtf8, commentNeeds8Bit;
- QStringList translations;
-
- for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) {
- //quint32 hash = read32(start);
- quint32 ro = read32(start + 4);
- //qDebug() << "\nHASH:" << hash;
- const uchar *m = messageArray + ro;
-
- for (;;) {
- uchar tag = read8(m++);
- //qDebug() << "Tag:" << tag << " ADDR: " << m;
- switch(tag) {
- case Tag_End:
- goto end;
- case Tag_Translation: {
- int len = read32(m);
- if (len % 1) {
- cd.appendError(QLatin1String("QM-Format error"));
- return false;
- }
- m += 4;
- QString str = QString((const QChar *)m, len/2);
- if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
- for (int i = 0; i < str.length(); ++i)
- str[i] = QChar((str.at(i).unicode() >> 8) +
- ((str.at(i).unicode() << 8) & 0xff00));
- }
- translations << str;
- m += len;
- break;
- }
- case Tag_Obsolete1:
- m += 4;
- //qDebug() << "OBSOLETE";
- break;
- case Tag_SourceText: {
- quint32 len = read32(m);
- m += 4;
- //qDebug() << "SOURCE LEN: " << len;
- //qDebug() << "SOURCE: " << QByteArray((const char*)m, len);
- fromBytes((const char*)m, len, codec, utf8Codec,
- &sourcetext, &sourcetextUtf8,
- &sourcetextIsSystem, &sourcetextIsUtf8, &sourcetextNeeds8Bit);
- m += len;
- break;
- }
- case Tag_Context: {
- quint32 len = read32(m);
- m += 4;
- //qDebug() << "CONTEXT LEN: " << len;
- //qDebug() << "CONTEXT: " << QByteArray((const char*)m, len);
- fromBytes((const char*)m, len, codec, utf8Codec,
- &context, &contextUtf8,
- &contextIsSystem, &contextIsUtf8, &contextNeeds8Bit);
- m += len;
- break;
- }
- case Tag_Comment: {
- quint32 len = read32(m);
- m += 4;
- //qDebug() << "COMMENT LEN: " << len;
- //qDebug() << "COMMENT: " << QByteArray((const char*)m, len);
- fromBytes((const char*)m, len, codec, utf8Codec,
- &comment, &commentUtf8,
- &commentIsSystem, &commentIsUtf8, &commentNeeds8Bit);
- m += len;
- break;
- }
- default:
- //qDebug() << "UNKNOWN TAG" << tag;
- break;
- }
- }
- end:;
- TranslatorMessage msg;
- msg.setType(TranslatorMessage::Finished);
- if (translations.count() > 1) {
- // If guessPlurals is not false here, plural form discard messages
- // will be spewn out later.
- msg.setPlural(true);
- } else if (guessPlurals) {
- // This might cause false positives, so it is a fallback only.
- if (sourcetext.contains(strProN))
- msg.setPlural(true);
- }
- msg.setTranslations(translations);
- translations.clear();
- if (contextNeeds8Bit || sourcetextNeeds8Bit || commentNeeds8Bit) {
- if (utf8Codec && contextIsUtf8 && sourcetextIsUtf8 && commentIsUtf8) {
- // The message is utf-8, but file is not.
- msg.setUtf8(true);
- msg.setContext(contextUtf8);
- msg.setSourceText(sourcetextUtf8);
- msg.setComment(commentUtf8);
- translator.append(msg);
- continue;
- }
- if (!(contextIsSystem && sourcetextIsSystem && commentIsSystem)) {
- cd.appendError(QLatin1String(
- "Cannot read file with specified input codec"));
- return false;
- }
- // The message is 8-bit in the file's encoding (utf-8 or not).
- }
- msg.setContext(context);
- msg.setSourceText(sourcetext);
- msg.setComment(comment);
- translator.append(msg);
- }
- return ok;
-}
-
-
-
-static bool containsStripped(const Translator &translator, const TranslatorMessage &msg)
-{
- foreach (const TranslatorMessage &tmsg, translator.messages())
- if (tmsg.sourceText() == msg.sourceText()
- && tmsg.context() == msg.context()
- && tmsg.comment().isEmpty())
- return true;
- return false;
-}
-
-bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- Releaser releaser;
- QLocale::Language l;
- QLocale::Country c;
- Translator::languageAndCountry(translator.languageCode(), &l, &c);
- QByteArray rules;
- if (getNumerusInfo(l, c, &rules, 0, 0))
- releaser.setNumerusRules(rules);
- releaser.setCodecName(translator.codecName());
-
- int finished = 0;
- int unfinished = 0;
- int untranslated = 0;
- int missingIds = 0;
- int droppedData = 0;
-
- for (int i = 0; i != translator.messageCount(); ++i) {
- const TranslatorMessage &msg = translator.message(i);
- TranslatorMessage::Type typ = msg.type();
- if (typ != TranslatorMessage::Obsolete) {
- if (cd.m_idBased && msg.id().isEmpty()) {
- ++missingIds;
- continue;
- }
- if (typ == TranslatorMessage::Unfinished) {
- if (msg.translation().isEmpty() && !cd.m_idBased && cd.m_unTrPrefix.isEmpty()) {
- ++untranslated;
- continue;
- } else {
- if (cd.ignoreUnfinished())
- continue;
- ++unfinished;
- }
- } else {
- ++finished;
- }
- QStringList tlns = msg.translations();
- if (msg.type() == TranslatorMessage::Unfinished
- && (cd.m_idBased || !cd.m_unTrPrefix.isEmpty()))
- for (int j = 0; j < tlns.size(); ++j)
- if (tlns.at(j).isEmpty())
- tlns[j] = cd.m_unTrPrefix + msg.sourceText();
- if (cd.m_idBased) {
- if (!msg.context().isEmpty() || !msg.comment().isEmpty())
- ++droppedData;
- releaser.insertIdBased(msg, tlns);
- } else {
- // Drop the comment in (context, sourceText, comment),
- // unless the context is empty,
- // unless (context, sourceText, "") already exists or
- // unless we already dropped the comment of (context,
- // sourceText, comment0).
- bool forceComment =
- msg.comment().isEmpty()
- || msg.context().isEmpty()
- || containsStripped(translator, msg);
- releaser.insert(msg, tlns, forceComment);
- }
- }
- }
-
- if (missingIds)
- cd.appendError(QCoreApplication::translate("LRelease",
- "Dropped %n message(s) which had no ID.", 0,
- QCoreApplication::CodecForTr, missingIds));
- if (droppedData)
- cd.appendError(QCoreApplication::translate("LRelease",
- "Excess context/disambiguation dropped from %n message(s).", 0,
- QCoreApplication::CodecForTr, droppedData));
-
- releaser.squeeze(cd.m_saveMode);
- bool saved = releaser.save(&dev);
- if (saved && cd.isVerbose()) {
- int generatedCount = finished + unfinished;
- cd.appendError(QCoreApplication::translate("LRelease",
- " Generated %n translation(s) (%1 finished and %2 unfinished)", 0,
- QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished));
- if (untranslated)
- cd.appendError(QCoreApplication::translate("LRelease",
- " Ignored %n untranslated source text(s)", 0,
- QCoreApplication::CodecForTr, untranslated));
- }
- return saved;
-}
-
-int initQM()
-{
- Translator::FileFormat format;
-
- format.extension = QLatin1String("qm");
- format.description = QObject::tr("Compiled Qt translations");
- format.fileType = Translator::FileFormat::TranslationBinary;
- format.priority = 0;
- format.loader = &loadQM;
- format.saver = &saveQM;
- Translator::registerFileFormat(format);
-
- return 1;
-}
-
-Q_CONSTRUCTOR_FUNCTION(initQM)
-
-QT_END_NAMESPACE
+++ /dev/null
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Qt Linguist of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** As a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "translator.h"
-
-#include <QtCore/QByteArray>
-#include <QtCore/QDebug>
-#include <QtCore/QTextCodec>
-#include <QtCore/QTextStream>
-
-#include <QtXml/QXmlStreamReader>
-#include <QtXml/qxmlstream.h>
-
-QT_BEGIN_NAMESPACE
-
-class QPHReader : public QXmlStreamReader
-{
-public:
- QPHReader(QIODevice &dev)
- : QXmlStreamReader(&dev)
- {}
-
- // the "real thing"
- bool read(Translator &translator);
-
-private:
- bool isWhiteSpace() const
- {
- return isCharacters() && text().toString().trimmed().isEmpty();
- }
-
- enum DataField { NoField, SourceField, TargetField, DefinitionField };
- DataField m_currentField;
- QString m_currentSource;
- QString m_currentTarget;
- QString m_currentDefinition;
-};
-
-bool QPHReader::read(Translator &translator)
-{
- m_currentField = NoField;
- QString result;
- while (!atEnd()) {
- readNext();
- if (isStartElement()) {
- if (name() == QLatin1String("source")) {
- m_currentField = SourceField;
- } else if (name() == QLatin1String("target")) {
- m_currentField = TargetField;
- } else if (name() == QLatin1String("definition")) {
- m_currentField = DefinitionField;
- } else {
- m_currentField = NoField;
- if (name() == QLatin1String("QPH")) {
- QXmlStreamAttributes atts = attributes();
- translator.setLanguageCode(atts.value(QLatin1String("language")).toString());
- translator.setSourceLanguageCode(atts.value(QLatin1String("sourcelanguage")).toString());
- }
- }
- } else if (isWhiteSpace()) {
- // ignore these
- } else if (isCharacters()) {
- if (m_currentField == SourceField)
- m_currentSource += text();
- else if (m_currentField == TargetField)
- m_currentTarget += text();
- else if (m_currentField == DefinitionField)
- m_currentDefinition += text();
- } else if (isEndElement() && name() == QLatin1String("phrase")) {
- m_currentTarget.replace(QChar(Translator::TextVariantSeparator),
- QChar(Translator::BinaryVariantSeparator));
- TranslatorMessage msg;
- msg.setSourceText(m_currentSource);
- msg.setTranslation(m_currentTarget);
- msg.setComment(m_currentDefinition);
- translator.append(msg);
- m_currentSource.clear();
- m_currentTarget.clear();
- m_currentDefinition.clear();
- }
- }
- return true;
-}
-
-static bool loadQPH(Translator &translator, QIODevice &dev, ConversionData &)
-{
- translator.setLocationsType(Translator::NoLocations);
- QPHReader reader(dev);
- return reader.read(translator);
-}
-
-static QString protect(const QString &str)
-{
- QString result;
- result.reserve(str.length() * 12 / 10);
- for (int i = 0; i != str.size(); ++i) {
- uint c = str.at(i).unicode();
- switch (c) {
- case '\"':
- result += QLatin1String(""");
- break;
- case '&':
- result += QLatin1String("&");
- break;
- case '>':
- result += QLatin1String(">");
- break;
- case '<':
- result += QLatin1String("<");
- break;
- case '\'':
- result += QLatin1String("'");
- break;
- default:
- if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
- result += QString(QLatin1String("&#%1;")).arg(c);
- else // this also covers surrogates
- result += QChar(c);
- }
- }
- return result;
-}
-
-static bool saveQPH(const Translator &translator, QIODevice &dev, ConversionData &)
-{
- QTextStream t(&dev);
- t.setCodec(QTextCodec::codecForName("UTF-8"));
- t << "<!DOCTYPE QPH>\n<QPH";
- QString languageCode = translator.languageCode();
- if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
- t << " language=\"" << languageCode << "\"";
- languageCode = translator.sourceLanguageCode();
- if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
- t << " sourcelanguage=\"" << languageCode << "\"";
- t << ">\n";
- foreach (const TranslatorMessage &msg, translator.messages()) {
- t << "<phrase>\n";
- t << " <source>" << protect(msg.sourceText()) << "</source>\n";
- QString str = msg.translations().join(QLatin1String("@"));
- str.replace(QChar(Translator::BinaryVariantSeparator),
- QChar(Translator::TextVariantSeparator));
- t << " <target>" << protect(str)
- << "</target>\n";
- if (!msg.comment().isEmpty())
- t << " <definition>" << protect(msg.comment()) << "</definition>\n";
- t << "</phrase>\n";
- }
- t << "</QPH>\n";
- return true;
-}
-
-int initQPH()
-{
- Translator::FileFormat format;
-
- format.extension = QLatin1String("qph");
- format.description = QObject::tr("Qt Linguist 'Phrase Book'");
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = 0;
- format.loader = &loadQPH;
- format.saver = &saveQPH;
- Translator::registerFileFormat(format);
-
- return 1;
-}
-
-Q_CONSTRUCTOR_FUNCTION(initQPH)
-
-QT_END_NAMESPACE
+++ /dev/null
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Qt Linguist of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 or version 3 as published by the Free
-** Software Foundation and appearing in the file LICENSE.LGPLv21 and
-** LICENSE.LGPLv3 included in the packaging of this file. Please review the
-** following information to ensure the GNU Lesser General Public License
-** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** As a special exception, The Qt Company gives you certain additional
-** rights. These rights are described in The Qt Company LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "translator.h"
-
-#include <QtCore/QByteArray>
-#include <QtCore/QDebug>
-#include <QtCore/QTextCodec>
-#include <QtCore/QTextStream>
-
-#include <QtXml/QXmlStreamReader>
-#include <QtXml/qxmlstream.h>
-
-#define STRINGIFY_INTERNAL(x) #x
-#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
-#define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s)))
-
-QT_BEGIN_NAMESPACE
-
-/*
- * The encodings are a total mess.
- * A Translator has a codecForTr(). Each message's text will be passed to tr()
- * in that encoding or as UTF-8 to trUtf8() if it is flagged as such.
- * For ts 2.0, the file content is always uniformly in UTF-8. The file stores
- * the codecForTr default and marks deviating messages accordingly.
- * For ts 1.1, the file content is in mixed encoding. Each message is encoded
- * the way it will be passed to tr() (with 8-bit characters encoded as numeric
- * entities) or trUtf8(). The file stores the encoding and codecForTr in one
- * attribute, for both the default and each deviating message.
- */
-
-
-QDebug &operator<<(QDebug &d, const QXmlStreamAttribute &attr)
-{
- return d << "[" << attr.name().toString() << "," << attr.value().toString() << "]";
-}
-
-
-class TSReader : public QXmlStreamReader
-{
-public:
- TSReader(QIODevice &dev, ConversionData &cd)
- : QXmlStreamReader(&dev), m_cd(cd)
- {}
-
- // the "real thing"
- bool read(Translator &translator);
-
-private:
- bool elementStarts(const QString &str) const
- {
- return isStartElement() && name() == str;
- }
-
- bool isWhiteSpace() const
- {
- return isCharacters() && text().toString().trimmed().isEmpty();
- }
-
- // needed to expand <byte ... />
- QString readContents();
- // needed to join <lengthvariant>s
- QString readTransContents();
-
- void handleError();
-
- ConversionData &m_cd;
-};
-
-void TSReader::handleError()
-{
- if (isComment())
- return;
- if (hasError() && error() == CustomError) // raised by readContents
- return;
-
- const QString loc = QString::fromLatin1("at %3:%1:%2")
- .arg(lineNumber()).arg(columnNumber()).arg(m_cd.m_sourceFileName);
-
- switch (tokenType()) {
- case NoToken: // Cannot happen
- default: // likewise
- case Invalid:
- raiseError(QString::fromLatin1("Parse error %1: %2").arg(loc, errorString()));
- break;
- case StartElement:
- raiseError(QString::fromLatin1("Unexpected tag <%1> %2").arg(name().toString(), loc));
- break;
- case Characters:
- {
- QString tok = text().toString();
- if (tok.length() > 30)
- tok = tok.left(30) + QLatin1String("[...]");
- raiseError(QString::fromLatin1("Unexpected characters '%1' %2").arg(tok, loc));
- }
- break;
- case EntityReference:
- raiseError(QString::fromLatin1("Unexpected entity '&%1;' %2").arg(name().toString(), loc));
- break;
- case ProcessingInstruction:
- raiseError(QString::fromLatin1("Unexpected processing instruction %1").arg(loc));
- break;
- }
-}
-
-static QString byteValue(QString value)
-{
- int base = 10;
- if (value.startsWith(QLatin1String("x"))) {
- base = 16;
- value.remove(0, 1);
- }
- int n = value.toUInt(0, base);
- return (n != 0) ? QString(QChar(n)) : QString();
-}
-
-QString TSReader::readContents()
-{
- STRING(byte);
- STRING(value);
-
- QString result;
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- break;
- } else if (isCharacters()) {
- result += text();
- } else if (elementStarts(strbyte)) {
- // <byte value="...">
- result += byteValue(attributes().value(strvalue).toString());
- readNext();
- if (!isEndElement()) {
- handleError();
- break;
- }
- } else {
- handleError();
- break;
- }
- }
- //qDebug() << "TEXT: " << result;
- return result;
-}
-
-QString TSReader::readTransContents()
-{
- STRING(lengthvariant);
- STRING(variants);
- STRING(yes);
-
- if (attributes().value(strvariants) == stryes) {
- QString result;
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- break;
- } else if (isWhiteSpace()) {
- // ignore these, just whitespace
- } else if (elementStarts(strlengthvariant)) {
- if (!result.isEmpty())
- result += QChar(Translator::BinaryVariantSeparator);
- result += readContents();
- } else {
- handleError();
- break;
- }
- }
- return result;
- } else {
- return readContents();
- }
-}
-
-bool TSReader::read(Translator &translator)
-{
- STRING(both);
- STRING(byte);
- STRING(comment);
- STRING(context);
- STRING(defaultcodec);
- STRING(encoding);
- STRING(extracomment);
- STRING(filename);
- STRING(id);
- STRING(language);
- STRING(line);
- STRING(location);
- STRING(message);
- STRING(name);
- STRING(numerus);
- STRING(numerusform);
- STRING(obsolete);
- STRING(oldcomment);
- STRING(oldsource);
- STRING(source);
- STRING(sourcelanguage);
- STRING(translation);
- STRING(translatorcomment);
- STRING(true);
- STRING(TS);
- STRING(type);
- STRING(unfinished);
- STRING(userdata);
- STRING(utf8);
- STRING(value);
- //STRING(version);
- STRING(yes);
-
- static const QString strextrans(QLatin1String("extra-"));
- static const QString strUtf8(QLatin1String("UTF-8"));
-
- while (!atEnd()) {
- readNext();
- if (isStartDocument()) {
- // <!DOCTYPE TS>
- //qDebug() << attributes();
- } else if (isEndDocument()) {
- // <!DOCTYPE TS>
- //qDebug() << attributes();
- } else if (isDTD()) {
- // <!DOCTYPE TS>
- //qDebug() << tokenString();
- } else if (elementStarts(strTS)) {
- // <TS>
- //qDebug() << "TS " << attributes();
- QHash<QString, int> currentLine;
- QString currentFile;
- bool maybeRelative = false, maybeAbsolute = false;
-
- QXmlStreamAttributes atts = attributes();
- //QString version = atts.value(strversion).toString();
- translator.setLanguageCode(atts.value(strlanguage).toString());
- translator.setSourceLanguageCode(atts.value(strsourcelanguage).toString());
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- // </TS> found, finish local loop
- break;
- } else if (isWhiteSpace()) {
- // ignore these, just whitespace
- } else if (elementStarts(strdefaultcodec)) {
- // <defaultcodec>
- const QString &codec = readElementText();
- if (!codec.isEmpty())
- translator.setCodecName(codec.toLatin1());
- // </defaultcodec>
- } else if (isStartElement()
- && name().toString().startsWith(strextrans)) {
- // <extra-...>
- QString tag = name().toString();
- translator.setExtra(tag.mid(6), readContents());
- // </extra-...>
- } else if (elementStarts(strcontext)) {
- // <context>
- QString context;
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- // </context> found, finish local loop
- break;
- } else if (isWhiteSpace()) {
- // ignore these, just whitespace
- } else if (elementStarts(strname)) {
- // <name>
- context = readElementText();
- // </name>
- } else if (elementStarts(strmessage)) {
- // <message>
- TranslatorMessage::References refs;
- QString currentMsgFile = currentFile;
-
- TranslatorMessage msg;
- msg.setId(attributes().value(strid).toString());
- msg.setContext(context);
- msg.setType(TranslatorMessage::Finished);
- msg.setPlural(attributes().value(strnumerus) == stryes);
- const QStringRef &utf8Attr = attributes().value(strutf8);
- msg.setNonUtf8(utf8Attr == strboth);
- msg.setUtf8(msg.isNonUtf8() || utf8Attr == strtrue
- || attributes().value(strencoding) == strUtf8);
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- // </message> found, finish local loop
- msg.setReferences(refs);
- translator.append(msg);
- break;
- } else if (isWhiteSpace()) {
- // ignore these, just whitespace
- } else if (elementStarts(strsource)) {
- // <source>...</source>
- msg.setSourceText(readContents());
- } else if (elementStarts(stroldsource)) {
- // <oldsource>...</oldsource>
- msg.setOldSourceText(readContents());
- } else if (elementStarts(stroldcomment)) {
- // <oldcomment>...</oldcomment>
- msg.setOldComment(readContents());
- } else if (elementStarts(strextracomment)) {
- // <extracomment>...</extracomment>
- msg.setExtraComment(readContents());
- } else if (elementStarts(strtranslatorcomment)) {
- // <translatorcomment>...</translatorcomment>
- msg.setTranslatorComment(readContents());
- } else if (elementStarts(strlocation)) {
- // <location/>
- maybeAbsolute = true;
- QXmlStreamAttributes atts = attributes();
- QString fileName = atts.value(strfilename).toString();
- if (fileName.isEmpty()) {
- fileName = currentMsgFile;
- maybeRelative = true;
- } else {
- if (refs.isEmpty())
- currentFile = fileName;
- currentMsgFile = fileName;
- }
- const QString lin = atts.value(strline).toString();
- if (lin.isEmpty()) {
- refs.append(TranslatorMessage::Reference(fileName, -1));
- } else {
- bool bOK;
- int lineNo = lin.toInt(&bOK);
- if (bOK) {
- if (lin.startsWith(QLatin1Char('+')) || lin.startsWith(QLatin1Char('-'))) {
- lineNo = (currentLine[fileName] += lineNo);
- maybeRelative = true;
- }
- refs.append(TranslatorMessage::Reference(fileName, lineNo));
- }
- }
- readContents();
- } else if (elementStarts(strcomment)) {
- // <comment>...</comment>
- msg.setComment(readContents());
- } else if (elementStarts(struserdata)) {
- // <userdata>...</userdata>
- msg.setUserData(readContents());
- } else if (elementStarts(strtranslation)) {
- // <translation>
- QXmlStreamAttributes atts = attributes();
- QStringRef type = atts.value(strtype);
- if (type == strunfinished)
- msg.setType(TranslatorMessage::Unfinished);
- else if (type == strobsolete)
- msg.setType(TranslatorMessage::Obsolete);
- if (msg.isPlural()) {
- QStringList translations;
- while (!atEnd()) {
- readNext();
- if (isEndElement()) {
- break;
- } else if (isWhiteSpace()) {
- // ignore these, just whitespace
- } else if (elementStarts(strnumerusform)) {
- translations.append(readTransContents());
- } else {
- handleError();
- break;
- }
- }
- msg.setTranslations(translations);
- } else {
- msg.setTranslation(readTransContents());
- }
- // </translation>
- } else if (isStartElement()
- && name().toString().startsWith(strextrans)) {
- // <extra-...>
- QString tag = name().toString();
- msg.setExtra(tag.mid(6), readContents());
- // </extra-...>
- } else {
- handleError();
- }
- }
- // </message>
- } else {
- handleError();
- }
- }
- // </context>
- } else {
- handleError();
- }
- translator.setLocationsType(maybeRelative ? Translator::RelativeLocations :
- maybeAbsolute ? Translator::AbsoluteLocations :
- Translator::NoLocations);
- } // </TS>
- } else {
- handleError();
- }
- }
- if (hasError()) {
- m_cd.appendError(errorString());
- return false;
- }
- return true;
-}
-
-static QString numericEntity(int ch)
-{
- return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>")
- : QLatin1String("&#x%1;")) .arg(ch, 0, 16);
-}
-
-static QString protect(const QString &str)
-{
- QString result;
- result.reserve(str.length() * 12 / 10);
- for (int i = 0; i != str.size(); ++i) {
- uint c = str.at(i).unicode();
- switch (c) {
- case '\"':
- result += QLatin1String(""");
- break;
- case '&':
- result += QLatin1String("&");
- break;
- case '>':
- result += QLatin1String(">");
- break;
- case '<':
- result += QLatin1String("<");
- break;
- case '\'':
- result += QLatin1String("'");
- break;
- default:
- if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
- result += numericEntity(c);
- else // this also covers surrogates
- result += QChar(c);
- }
- }
- return result;
-}
-
-static QString evilBytes(const QString& str,
- bool isUtf8, int format, const QByteArray &codecName)
-{
- //qDebug() << "EVIL: " << str << isUtf8 << format << codecName;
- if (isUtf8)
- return protect(str);
- if (format == 20)
- return protect(str);
- if (codecName == "UTF-8")
- return protect(str);
- QTextCodec *codec = QTextCodec::codecForName(codecName);
- if (!codec)
- return protect(str);
- QString t = QString::fromLatin1(codec->fromUnicode(protect(str)).data());
- int len = (int) t.length();
- QString result;
- // FIXME: Factor is sensible only for latin scripts, probably.
- result.reserve(t.length() * 2);
- for (int k = 0; k < len; k++) {
- if (t[k].unicode() >= 0x7f)
- result += numericEntity(t[k].unicode());
- else
- result += t[k];
- }
- return result;
-}
-
-static void writeExtras(QTextStream &t, const char *indent,
- const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
-{
- for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
- if (!drops.exactMatch(it.key())) {
- t << indent << "<extra-" << it.key() << '>'
- << protect(it.value())
- << "</extra-" << it.key() << ">\n";
- }
- }
-}
-
-static void writeVariants(QTextStream &t, const char *indent, const QString &input)
-{
- int offset;
- if ((offset = input.indexOf(QChar(Translator::BinaryVariantSeparator))) >= 0) {
- t << " variants=\"yes\">";
- int start = 0;
- forever {
- t << "\n " << indent << "<lengthvariant>"
- << protect(input.mid(start, offset - start))
- << "</lengthvariant>";
- if (offset == input.length())
- break;
- start = offset + 1;
- offset = input.indexOf(QChar(Translator::BinaryVariantSeparator), start);
- if (offset < 0)
- offset = input.length();
- }
- t << "\n" << indent;
- } else {
- t << ">" << protect(input);
- }
-}
-
-bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd, int format)
-{
- bool result = true;
- QTextStream t(&dev);
- t.setCodec(QTextCodec::codecForName("UTF-8"));
- bool trIsUtf8 = (translator.codecName() == "UTF-8");
- //qDebug() << translator.codecName();
- bool fileIsUtf8 = (format == 20 || trIsUtf8);
-
- // The xml prolog allows processors to easily detect the correct encoding
- t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n";
-
- if (format == 11)
- t << "<TS version=\"1.1\"";
- else
- t << "<TS version=\"2.0\"";
-
- QString languageCode = translator.languageCode();
- if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
- t << " language=\"" << languageCode << "\"";
- if (format == 20) {
- languageCode = translator.sourceLanguageCode();
- if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
- t << " sourcelanguage=\"" << languageCode << "\"";
- }
- t << ">\n";
-
- QByteArray codecName = translator.codecName();
- if (codecName != "ISO-8859-1")
- t << "<defaultcodec>" << codecName << "</defaultcodec>\n";
-
- QRegExp drops(cd.dropTags().join(QLatin1String("|")));
-
- if (format == 20)
- writeExtras(t, " ", translator.extras(), drops);
-
- QHash<QString, QList<TranslatorMessage> > messageOrder;
- QList<QString> contextOrder;
- foreach (const TranslatorMessage &msg, translator.messages()) {
- // no need for such noise
- if (msg.type() == TranslatorMessage::Obsolete && msg.translation().isEmpty())
- continue;
-
- QList<TranslatorMessage> &context = messageOrder[msg.context()];
- if (context.isEmpty())
- contextOrder.append(msg.context());
- context.append(msg);
- }
- if (cd.sortContexts())
- qSort(contextOrder);
-
- QHash<QString, int> currentLine;
- QString currentFile;
- foreach (const QString &context, contextOrder) {
- const TranslatorMessage &firstMsg = messageOrder[context].first();
- t << "<context" << ((!fileIsUtf8 && firstMsg.isUtf8()) ? " encoding=\"UTF-8\"" : "") << ">\n";
-
- t << " <name>"
- << evilBytes(context, firstMsg.isUtf8() || fileIsUtf8, format, codecName)
- << "</name>\n";
- foreach (const TranslatorMessage &msg, messageOrder[context]) {
- //msg.dump();
-
- bool isUtf8 = msg.isUtf8();
- bool second = false;
- forever {
-
- t << " <message";
- if (!msg.id().isEmpty())
- t << " id=\"" << msg.id() << "\"";
- if (!trIsUtf8) {
- if (format == 11) {
- if (isUtf8)
- t << " encoding=\"UTF-8\"";
- } else {
- if (msg.isUtf8()) {
- if (msg.isNonUtf8())
- t << " utf8=\"both\"";
- else
- t << " utf8=\"true\"";
- }
- }
- }
- if (msg.isPlural())
- t << " numerus=\"yes\"";
- t << ">\n";
- if (translator.locationsType() != Translator::NoLocations) {
- QString cfile = currentFile;
- bool first = true;
- foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) {
- QString fn = cd.m_targetDir.relativeFilePath(ref.fileName())
- .replace(QLatin1Char('\\'),QLatin1Char('/'));
- int ln = ref.lineNumber();
- QString ld;
- if (translator.locationsType() == Translator::RelativeLocations) {
- if (ln != -1) {
- int dlt = ln - currentLine[fn];
- if (dlt >= 0)
- ld.append(QLatin1Char('+'));
- ld.append(QString::number(dlt));
- currentLine[fn] = ln;
- }
-
- if (fn != cfile) {
- if (first)
- currentFile = fn;
- cfile = fn;
- } else {
- fn.clear();
- }
- first = false;
- } else {
- if (ln != -1)
- ld = QString::number(ln);
- }
- t << " <location";
- if (!fn.isEmpty())
- t << " filename=\"" << fn << "\"";
- if (!ld.isEmpty())
- t << " line=\"" << ld << "\"";
- t << "/>\n";
- }
- }
-
- t << " <source>"
- << evilBytes(msg.sourceText(), isUtf8, format, codecName)
- << "</source>\n";
-
- if (format != 11 && !msg.oldSourceText().isEmpty())
- t << " <oldsource>" << protect(msg.oldSourceText()) << "</oldsource>\n";
-
- if (!msg.comment().isEmpty()) {
- t << " <comment>"
- << evilBytes(msg.comment(), isUtf8, format, codecName)
- << "</comment>\n";
- }
-
- if (format != 11) {
-
- if (!msg.oldComment().isEmpty())
- t << " <oldcomment>" << protect(msg.oldComment()) << "</oldcomment>\n";
-
- if (!msg.extraComment().isEmpty())
- t << " <extracomment>" << protect(msg.extraComment())
- << "</extracomment>\n";
-
- if (!msg.translatorComment().isEmpty())
- t << " <translatorcomment>" << protect(msg.translatorComment())
- << "</translatorcomment>\n";
-
- }
-
- t << " <translation";
- if (msg.type() == TranslatorMessage::Unfinished)
- t << " type=\"unfinished\"";
- else if (msg.type() == TranslatorMessage::Obsolete)
- t << " type=\"obsolete\"";
- if (msg.isPlural()) {
- t << ">";
- const QStringList &translns = msg.translations();
- for (int j = 0; j < translns.count(); ++j) {
- t << "\n <numerusform";
- writeVariants(t, " ", translns[j]);
- t << "</numerusform>";
- }
- t << "\n ";
- } else {
- writeVariants(t, " ", msg.translation());
- }
- t << "</translation>\n";
-
- if (format != 11)
- writeExtras(t, " ", msg.extras(), drops);
-
- if (!msg.userData().isEmpty())
- t << " <userdata>" << msg.userData() << "</userdata>\n";
- t << " </message>\n";
-
- if (format != 11 || second || !msg.isUtf8() || !msg.isNonUtf8())
- break;
- isUtf8 = false;
- second = true;
- }
- }
- t << "</context>\n";
- }
-
- t << "</TS>\n";
- return result;
-}
-
-bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- TSReader reader(dev, cd);
- return reader.read(translator);
-}
-
-bool saveTS11(const Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- return saveTS(translator, dev, cd, 11);
-}
-
-bool saveTS20(const Translator &translator, QIODevice &dev, ConversionData &cd)
-{
- return saveTS(translator, dev, cd, 20);
-}
-
-int initTS()
-{
- Translator::FileFormat format;
-
- format.extension = QLatin1String("ts11");
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = -1;
- format.description = QObject::tr("Qt translation sources (format 1.1)");
- format.loader = &loadTS;
- format.saver = &saveTS11;
- Translator::registerFileFormat(format);
-
- format.extension = QLatin1String("ts20");
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = -1;
- format.description = QObject::tr("Qt translation sources (format 2.0)");
- format.loader = &loadTS;
- format.saver = &saveTS20;
- Translator::registerFileFormat(format);
-
- // "ts" is always the latest. right now it's ts20.
- format.extension = QLatin1String("ts");
- format.fileType = Translator::FileFormat::TranslationSource;
- format.priority = 0;
- format.description = QObject::tr("Qt translation sources (latest format)");
- format.loader = &loadTS;
- format.saver = &saveTS20;
- Translator::registerFileFormat(format);
-
- return 1;
-}
-
-Q_CONSTRUCTOR_FUNCTION(initTS)
-
-QT_END_NAMESPACE