Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include "trparser.h"
++ #include "translator.h"
++ #include <iostream>
++
++ using namespace Qt::StringLiterals;
++
++ QT_BEGIN_NAMESPACE
++
++ static bool processTs(Translator &fetchedTor, const QString &file, ConversionData &cd)
++ {
++ for (const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
++ if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
++ Translator tor;
++ if (tor.load(file, cd, fmt.extension)) {
++ for (TranslatorMessage msg : tor.messages()) {
++ msg.setType(TranslatorMessage::Unfinished);
++ msg.setTranslations(QStringList());
++ msg.setTranslatorComment(QString());
++ fetchedTor.extend(msg, cd);
++ }
++ }
++ return true;
++ }
++ }
++ return false;
++ }
++
++ void processSources(Translator &fetchedTor, const QStringList &sourceFiles, ConversionData &cd)
++ {
++ #ifdef QT_NO_QML
++ bool requireQmlSupport = false;
++ #endif
++ QStringList sourceFilesCpp;
++ for (const auto &sourceFile : sourceFiles) {
++ if (sourceFile.endsWith(".java"_L1, Qt::CaseInsensitive))
++ loadJava(fetchedTor, sourceFile, cd);
++ else if (sourceFile.endsWith(".ui"_L1, Qt::CaseInsensitive)
++ || sourceFile.endsWith(".jui"_L1, Qt::CaseInsensitive))
++ loadUI(fetchedTor, sourceFile, cd);
++ #ifndef QT_NO_QML
++ else if (sourceFile.endsWith(".js"_L1, Qt::CaseInsensitive)
++ || sourceFile.endsWith(".qs"_L1, Qt::CaseInsensitive)) {
++ loadQScript(fetchedTor, sourceFile, cd);
++ } else if (sourceFile.endsWith(".mjs"_L1, Qt::CaseInsensitive)) {
++ loadJSModule(fetchedTor, sourceFile, cd);
++ } else if (sourceFile.endsWith(".qml"_L1, Qt::CaseInsensitive))
++ loadQml(fetchedTor, sourceFile, cd);
++ #else
++ else if (sourceFile.endsWith(".qml"_L1, Qt::CaseInsensitive)
++ || sourceFile.endsWith(".js"_L1, Qt::CaseInsensitive)
++ || sourceFile.endsWith(".mjs"_L1, Qt::CaseInsensitive)
++ || sourceFile.endsWith(".qs"_L1, Qt::CaseInsensitive))
++ requireQmlSupport = true;
++ #endif // QT_NO_QML
++ else if (sourceFile.endsWith(u".py", Qt::CaseInsensitive))
++ loadPython(fetchedTor, sourceFile, cd);
++ else if (!processTs(fetchedTor, sourceFile, cd))
++ sourceFilesCpp << sourceFile;
++ }
++
++ #ifdef QT_NO_QML
++ if (requireQmlSupport) {
++ std::cout << qPrintable("missing qml/javascript support\n"
++ "Some files have been ignored.\n"_L1);
++ }
++ #endif
++
++ loadCPP(fetchedTor, sourceFilesCpp, cd);
++
++ if (!cd.error().isEmpty())
++ std::cerr << qPrintable(cd.error());
++ }
++
++ QString transcode(const QString &str)
++ {
++ static const char tab[] = "abfnrtv";
++ static const char backTab[] = "\a\b\f\n\r\t\v";
++ // This function has to convert back to bytes, as C's \0* sequences work at that level.
++ const QByteArray in = str.toUtf8();
++ QByteArray out;
++
++ out.reserve(in.size());
++ for (qsizetype i = 0; i < in.size();) {
++ uchar c = in[i++];
++ if (c == '\\') {
++ if (i >= in.size())
++ break;
++ c = in[i++];
++
++ if (c == '\n')
++ continue;
++
++ if (c == 'x' || c == 'u' || c == 'U') {
++ qsizetype maxSize = 2; // \x
++ if (c == 'u')
++ maxSize = 4;
++ else if (c == 'U')
++ maxSize = 8;
++ maxSize = std::min(in.size(), i + maxSize);
++
++ const bool unicode = (c != 'x');
++ QByteArray hex;
++ while (i < maxSize && isxdigit((c = in[i]))) {
++ hex += c;
++ i++;
++ }
++ if (unicode)
++ out += QString(QChar(hex.toUInt(nullptr, 16))).toUtf8();
++ else
++ out += hex.toUInt(nullptr, 16);
++ } else if (c >= '0' && c < '8') {
++ QByteArray oct;
++ int n = 0;
++ oct += c;
++ while (n < 2 && i < in.size() && (c = in[i]) >= '0' && c < '8') {
++ i++;
++ n++;
++ oct += c;
++ }
++ out += oct.toUInt(0, 8);
++ } else {
++ const char *p = strchr(tab, c);
++ out += !p ? c : backTab[p - tab];
++ }
++ } else {
++ out += c;
++ }
++ }
++ return QString::fromUtf8(out.constData(), out.size());
++ }
++
++ // Can't have an array of QStaticStringData<N> for different N, so
++ // use QString, which requires constructor calls. Doesn't matter
++ // much, since this is in an app, not a lib:
++ static const QString defaultTrFunctionNames[] = {
++ // MSVC can't handle the lambda in this array if QStringLiteral expands
++ // to a lambda. In that case, use a QString instead.
++ #if defined(Q_CC_MSVC) && defined(Q_COMPILER_LAMBDA)
++ # define STRINGLITERAL(F) QLatin1String(#F),
++ #else
++ # define STRINGLITERAL(F) QStringLiteral(#F),
++ #endif
++ LUPDATE_FOR_EACH_TR_FUNCTION(STRINGLITERAL)
++ #undef STRINGLITERAL
++ };
++
++ Q_STATIC_ASSERT((TrFunctionAliasManager::NumTrFunctions
++ == sizeof defaultTrFunctionNames / sizeof *defaultTrFunctionNames));
++
++ TrFunctionAliasManager trFunctionAliasManager;
++
++ QStringList availableFunctions()
++ {
++ QStringList result;
++ result.reserve(TrFunctionAliasManager::NumTrFunctions);
++ for (int i = 0; i < TrFunctionAliasManager::NumTrFunctions; ++i)
++ result.push_back(defaultTrFunctionNames[i]);
++ return result;
++ }
++
++ int trFunctionByDefaultName(const QString &trFunctionName)
++ {
++ for (int i = 0; i < TrFunctionAliasManager::NumTrFunctions; ++i)
++ if (trFunctionName == defaultTrFunctionNames[i])
++ return i;
++ return -1;
++ }
++
++ static void printErr(const QString &out)
++ {
++ std::cerr << qPrintable(out);
++ }
++
++ bool parseTrFunctionAliasString(const QString &aliasString)
++ {
++ for (const QString &pair : aliasString.split(u',', Qt::SkipEmptyParts)) {
++ const int equalSign = pair.indexOf(u'=');
++ if (equalSign < 0) {
++ printErr("tr-function mapping '%1' in -tr-function-alias is missing the '='.\n"_L1.arg(
++ pair));
++ return false;
++ }
++ const bool plusEqual = equalSign > 0 && pair[equalSign - 1] == u'+';
++ const int trFunctionEnd = plusEqual ? equalSign - 1 : equalSign;
++ const QString trFunctionName = pair.left(trFunctionEnd).trimmed();
++ const QString alias = pair.mid(equalSign + 1).trimmed();
++ const int trFunction = trFunctionByDefaultName(trFunctionName);
++ if (trFunction < 0) {
++ printErr("Unknown tr-function '%1' in -tr-function-alias option.\n"
++ "Available tr-functions are: %2\n"_L1.arg(trFunctionName,
++ availableFunctions().join(u',')));
++ return false;
++ }
++ if (alias.isEmpty()) {
++ printErr("Empty alias for tr-function '%1' in -tr-function-alias option.\n"_L1.arg(
++ trFunctionName));
++ return false;
++ }
++ trFunctionAliasManager.modifyAlias(trFunction, alias,
++ plusEqual ? TrFunctionAliasManager::AddAlias
++ : TrFunctionAliasManager::SetAlias);
++ }
++ return true;
++ }
++
++ TrFunctionAliasManager::TrFunctionAliasManager() : m_trFunctionAliases()
++ {
++ for (int i = 0; i < NumTrFunctions; ++i)
++ m_trFunctionAliases[i].push_back(defaultTrFunctionNames[i]);
++ }
++
++ TrFunctionAliasManager::~TrFunctionAliasManager() { }
++
++ int TrFunctionAliasManager::trFunctionByName(const QString &trFunctionName) const
++ {
++ ensureTrFunctionHashUpdated();
++ // this function needs to be fast
++ const auto it = m_nameToTrFunctionMap.constFind(trFunctionName);
++ return it == m_nameToTrFunctionMap.cend() ? -1 : *it;
++ }
++
++ void TrFunctionAliasManager::modifyAlias(int trFunction, const QString &alias, Operation op)
++ {
++ QList<QString> &list = m_trFunctionAliases[trFunction];
++ if (op == SetAlias)
++ list.clear();
++ list.push_back(alias);
++ m_nameToTrFunctionMap.clear();
++ }
++
++ void TrFunctionAliasManager::ensureTrFunctionHashUpdated() const
++ {
++ if (!m_nameToTrFunctionMap.empty())
++ return;
++
++ NameToTrFunctionMap nameToTrFunctionMap;
++ for (int i = 0; i < NumTrFunctions; ++i)
++ for (const QString &alias : m_trFunctionAliases[i])
++ nameToTrFunctionMap[alias] = TrFunction(i);
++ // commit:
++ m_nameToTrFunctionMap.swap(nameToTrFunctionMap);
++ }
++
++ const TrFunctionAliasManager::NameToTrFunctionMap &
++ TrFunctionAliasManager::nameToTrFunctionMap() const
++ {
++ ensureTrFunctionHashUpdated();
++ return m_nameToTrFunctionMap;
++ }
++
++ QStringList TrFunctionAliasManager::availableFunctionsWithAliases() const
++ {
++ QStringList result;
++ result.reserve(NumTrFunctions);
++ for (int i = 0; i < NumTrFunctions; ++i)
++ result.push_back(defaultTrFunctionNames[i] + " (="_L1 + m_trFunctionAliases[i].join(u'=')
++ + u')');
++ return result;
++ }
++
++ QStringList TrFunctionAliasManager::listAliases() const
++ {
++ QStringList result;
++ result.reserve(NumTrFunctions);
++ for (int i = 0; i < NumTrFunctions; ++i) {
++ for (int ii = 1; ii < m_trFunctionAliases[i].size(); ii++) {
++ // ii = 0 is the default name. Not listed here
++ result.push_back(m_trFunctionAliases[i][ii]);
++ }
++ }
++ return result;
++ }
++
++ QT_END_NAMESPACE