Revision control
Copy as Markdown
// 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 "metastrings.h"
#include <QFileInfo>
namespace {
using namespace Qt::Literals::StringLiterals;
static constexpr QLatin1StringView PlaceholderClass = "<class>"_L1;
static constexpr QLatin1StringView PlaceholderContext = "<context>"_L1;
static constexpr QLatin1StringView PlaceholderFile = "<file>"_L1;
static constexpr QLatin1StringView UnnamedPlaceholder = "<unnamed>"_L1;
static constexpr QLatin1String CppMagicComment = "TRANSLATOR"_L1;
} // namespace
QT_BEGIN_NAMESPACE
bool MetaStrings::parse(QString &string)
{
const QChar *ptr = string.unicode();
if (*ptr == extraCommentAnotation && ptr[1].isSpace()) {
string.remove(0, 2);
m_extracomment += string;
if (!m_extracomment.endsWith(u'\n'))
m_extracomment.push_back(u'\n');
m_extracomment.detach();
} else if (*ptr == idAnotation && ptr[1].isSpace()) {
string.remove(0, 2);
m_msgid = string.simplified();
m_msgid.detach();
m_error =
"Setting translation IDs using //= or #= is deprecated and will be removed in the upcoming versions.\n"_L1;
} else if (*ptr == extraAnotation && ptr[1].isSpace()) {
string.remove(0, 2);
const QString trimmed = string.trimmed();
int k = trimmed.indexOf(u' ');
if (k > -1) {
QString commentvalue = trimmed.mid(k + 1).trimmed();
if (commentvalue.startsWith(u'"') && commentvalue.endsWith(u'"')
&& commentvalue.size() != 1) {
commentvalue = commentvalue.sliced(1, commentvalue.size() - 2);
}
m_extra.insert(trimmed.left(k), commentvalue);
}
} else if (*ptr == sourceTextAnotation && ptr[1].isSpace()) {
m_sourcetext.reserve(m_sourcetext.size() + string.size() - 2);
ushort *ptr = (ushort *)m_sourcetext.data() + m_sourcetext.size();
int p = 2, c;
forever {
if (p >= string.size())
break;
c = string.unicode()[p++].unicode();
if (isspace(c))
continue;
if (c != '"') {
m_error = "Unexpected character in meta string\n"_L1;
break;
}
forever {
if (p >= string.size()) {
whoops:
m_error = "Unterminated meta string\n"_L1;
break;
}
c = string.unicode()[p++].unicode();
if (c == '"')
break;
if (c == '\\') {
if (p >= string.size())
goto whoops;
c = string.unicode()[p++].unicode();
if (c == '\n')
goto whoops;
*ptr++ = '\\';
}
*ptr++ = c;
}
}
m_sourcetext.resize(ptr - (ushort *)m_sourcetext.data());
} else if (*ptr == labelAnotation && ptr[1].isSpace()) {
string.remove(0, 2);
m_label = string.trimmed().simplified();
m_label.detach();
} else if (const QString trimmed = string.trimmed(); trimmed.startsWith(CppMagicComment)) {
qsizetype idx = CppMagicComment.size();
QString comment =
QString::fromRawData(trimmed.unicode() + idx, trimmed.size() - idx).simplified();
if (int k = comment.indexOf(u' '); k != -1) {
QString context = comment.left(k);
comment.remove(0, k + 1);
m_magicComment.emplace(MagicComment{ std::move(context), std::move(comment) });
}
}
return m_error.isEmpty();
}
bool MetaStrings::resolveLabel(const QString &filename, const QString &context,
const QString &className)
{
if (m_label.contains(PlaceholderClass)) {
QString classStr = className;
if (classStr.isEmpty()) {
classStr = UnnamedPlaceholder;
m_error =
"Label placeholder <class> used, but no class available. Using <unnamed>.\n"_L1;
}
m_label.replace(PlaceholderClass, classStr);
}
if (m_label.contains(PlaceholderContext)) {
QString contextStr = context;
if (contextStr.isEmpty()) {
contextStr = UnnamedPlaceholder;
m_error +=
"Label placeholder <context> used, but no context available. Using <unnamed>.\n"_L1;
}
m_label.replace(PlaceholderContext, contextStr);
}
if (m_label.contains(PlaceholderFile))
m_label.replace(PlaceholderFile, QFileInfo(filename).fileName());
return m_error.isEmpty();
}
void MetaStrings::clear()
{
m_magicComment.reset();
m_extracomment.clear();
m_msgid.clear();
m_label.clear();
m_sourcetext.clear();
m_extra.clear();
}
bool MetaStrings::hasData() const
{
return !m_msgid.isEmpty() || m_magicComment || !m_sourcetext.isEmpty()
|| !m_extracomment.isEmpty() || !m_extra.isEmpty() || !m_label.isEmpty();
}
QT_END_NAMESPACE