diff options
Diffstat (limited to 'src/linguist')
28 files changed, 1202 insertions, 613 deletions
diff --git a/src/linguist/Qt5LinguistToolsConfig.cmake.in b/src/linguist/Qt5LinguistToolsConfig.cmake.in index 4318b16fa..2e99bc762 100644 --- a/src/linguist/Qt5LinguistToolsConfig.cmake.in +++ b/src/linguist/Qt5LinguistToolsConfig.cmake.in @@ -89,4 +89,15 @@ endif() set(Qt5_LRELEASE_EXECUTABLE Qt5::lrelease) set(Qt5_LUPDATE_EXECUTABLE Qt5::lupdate) +# Create versionless tool targets. +foreach(__qt_tool lrelease lupdate lconvert) + if(NOT \"${QT_NO_CREATE_VERSIONLESS_TARGETS}\" AND NOT TARGET Qt::${__qt_tool} + AND TARGET Qt5::${__qt_tool}) + add_executable(Qt::${__qt_tool} IMPORTED) + get_target_property(__qt_imported_location Qt5::${__qt_tool} IMPORTED_LOCATION) + set_target_properties(Qt::${__qt_tool} + PROPERTIES IMPORTED_LOCATION \"${__qt_imported_location}\") + endif() +endforeach() + include(\"${CMAKE_CURRENT_LIST_DIR}/Qt5LinguistToolsMacros.cmake\") diff --git a/src/linguist/Qt5LinguistToolsMacros.cmake b/src/linguist/Qt5LinguistToolsMacros.cmake index 23beeb397..ab271d56a 100644 --- a/src/linguist/Qt5LinguistToolsMacros.cmake +++ b/src/linguist/Qt5LinguistToolsMacros.cmake @@ -81,6 +81,17 @@ function(QT5_CREATE_TRANSLATION _qm_files) set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) endfunction() +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_create_translation _qm_files) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 5) + qt5_create_translation("${_qm_files}" ${ARGN}) + elseif(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_create_translation("${_qm_files}" ${ARGN}) + endif() + set("${_qm_files}" "${${_qm_files}}" PARENT_SCOPE) + endfunction() +endif() + function(QT5_ADD_TRANSLATION _qm_files) set(options) @@ -112,3 +123,14 @@ function(QT5_ADD_TRANSLATION _qm_files) endforeach() set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) endfunction() + +if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS) + function(qt_add_translation _qm_files) + if(QT_DEFAULT_MAJOR_VERSION EQUAL 5) + qt5_add_translation("${_qm_files}" ${ARGN}) + elseif(QT_DEFAULT_MAJOR_VERSION EQUAL 6) + qt6_add_translation("${_qm_files}" ${ARGN}) + endif() + set("${_qm_files}" "${${_qm_files}}" PARENT_SCOPE) + endfunction() +endif() diff --git a/src/linguist/linguist/linguist.pro b/src/linguist/linguist/linguist.pro index af74df8ec..d083896c3 100644 --- a/src/linguist/linguist/linguist.pro +++ b/src/linguist/linguist/linguist.pro @@ -1,4 +1,4 @@ -QT += core-private gui-private widgets xml uitools-private printsupport +QT += core-private gui-private widgets uitools-private printsupport DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII diff --git a/src/linguist/linguist/mainwindow.cpp b/src/linguist/linguist/mainwindow.cpp index 35ce52891..26a3d03f2 100644 --- a/src/linguist/linguist/mainwindow.cpp +++ b/src/linguist/linguist/mainwindow.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Linguist of the Qt Toolkit. @@ -299,7 +299,6 @@ MainWindow::MainWindow() m_contextDock = new QDockWidget(this); m_contextDock->setObjectName(QLatin1String("ContextDockWidget")); m_contextDock->setAllowedAreas(Qt::AllDockWidgetAreas); - m_contextDock->setFeatures(QDockWidget::AllDockWidgetFeatures); m_contextDock->setWindowTitle(tr("Context")); m_contextDock->setAcceptDrops(true); m_contextDock->installEventFilter(this); @@ -329,7 +328,6 @@ MainWindow::MainWindow() m_messagesDock = new QDockWidget(this); m_messagesDock->setObjectName(QLatin1String("StringsDockWidget")); m_messagesDock->setAllowedAreas(Qt::AllDockWidgetAreas); - m_messagesDock->setFeatures(QDockWidget::AllDockWidgetFeatures); m_messagesDock->setWindowTitle(tr("Strings")); m_messagesDock->setAcceptDrops(true); m_messagesDock->installEventFilter(this); @@ -366,7 +364,6 @@ MainWindow::MainWindow() m_phrasesDock = new QDockWidget(this); m_phrasesDock->setObjectName(QLatin1String("PhrasesDockwidget")); m_phrasesDock->setAllowedAreas(Qt::AllDockWidgetAreas); - m_phrasesDock->setFeatures(QDockWidget::AllDockWidgetFeatures); m_phrasesDock->setWindowTitle(tr("Phrases and guesses")); m_phraseView = new PhraseView(m_dataModel, &m_phraseDict, this); @@ -376,7 +373,6 @@ MainWindow::MainWindow() m_sourceAndFormDock = new QDockWidget(this); m_sourceAndFormDock->setObjectName(QLatin1String("SourceAndFormDock")); m_sourceAndFormDock->setAllowedAreas(Qt::AllDockWidgetAreas); - m_sourceAndFormDock->setFeatures(QDockWidget::AllDockWidgetFeatures); m_sourceAndFormDock->setWindowTitle(tr("Sources and Forms")); m_sourceAndFormView = new QStackedWidget(this); m_sourceAndFormDock->setWidget(m_sourceAndFormView); @@ -391,7 +387,6 @@ MainWindow::MainWindow() m_errorsDock = new QDockWidget(this); m_errorsDock->setObjectName(QLatin1String("ErrorsDockWidget")); m_errorsDock->setAllowedAreas(Qt::AllDockWidgetAreas); - m_errorsDock->setFeatures(QDockWidget::AllDockWidgetFeatures); m_errorsDock->setWindowTitle(tr("Warnings")); m_errorsView = new ErrorsView(m_dataModel, this); m_errorsDock->setWidget(m_errorsView); @@ -1360,7 +1355,7 @@ void MainWindow::about() const QString description = tr("Qt Linguist is a tool for adding translations to Qt applications."); const QString copyright - = tr("Copyright (C) %1 The Qt Company Ltd.").arg(QStringLiteral("2019")); + = tr("Copyright (C) %1 The Qt Company Ltd.").arg(QStringLiteral("2020")); box.setText(QStringLiteral("<center><img src=\":/images/icons/linguist-128-32.png\"/></img><p>%1</p></center>" "<p>%2</p>" "<p>%3</p>").arg(version, description, copyright)); diff --git a/src/linguist/linguist/messageeditorwidgets.cpp b/src/linguist/linguist/messageeditorwidgets.cpp index 6c16b36fd..6880f50fc 100644 --- a/src/linguist/linguist/messageeditorwidgets.cpp +++ b/src/linguist/linguist/messageeditorwidgets.cpp @@ -372,7 +372,7 @@ void FormMultiWidget::slotSelectionChanged() void FormMultiWidget::setTranslation(const QString &text, bool userAction) { - QStringList texts = text.split(QChar(Translator::BinaryVariantSeparator), QString::KeepEmptyParts); + QStringList texts = text.split(QChar(Translator::BinaryVariantSeparator), Qt::KeepEmptyParts); while (m_editors.count() > texts.count()) { delete m_minusButtons.takeLast(); @@ -418,7 +418,7 @@ QString FormMultiWidget::getTranslation() const for (int i = 0; i < m_editors.count(); ++i) { if (i) ret += QChar(Translator::BinaryVariantSeparator); - ret += toPlainText(m_editors.at(i)->document()->docHandle()->plainText()); + ret += toPlainText(m_editors.at(i)->document()->toRawText()); } return ret; } diff --git a/src/linguist/linguist/messagemodel.h b/src/linguist/linguist/messagemodel.h index 5c2d95ec2..32f868d51 100644 --- a/src/linguist/linguist/messagemodel.h +++ b/src/linguist/linguist/messagemodel.h @@ -37,8 +37,6 @@ #include <QtCore/QLocale> #include <QtGui/QColor> #include <QtGui/QBitmap> -#include <QtXml/QXmlDefaultHandler> - QT_BEGIN_NAMESPACE diff --git a/src/linguist/linguist/phrase.cpp b/src/linguist/linguist/phrase.cpp index b7b9835f5..064e79f40 100644 --- a/src/linguist/linguist/phrase.cpp +++ b/src/linguist/linguist/phrase.cpp @@ -28,6 +28,7 @@ #include "phrase.h" #include "translator.h" +#include "xmlparser.h" #include <QApplication> #include <QFile> @@ -36,9 +37,7 @@ #include <QRegExp> #include <QTextCodec> #include <QTextStream> -#include <QXmlAttributes> -#include <QXmlDefaultHandler> -#include <QXmlParseException> +#include <QXmlStreamReader> QT_BEGIN_NAMESPACE @@ -104,24 +103,26 @@ bool operator==(const Phrase &p, const Phrase &q) p.definition() == q.definition() && p.phraseBook() == q.phraseBook(); } -class QphHandler : public QXmlDefaultHandler +class QphHandler : public XmlParser { public: - QphHandler(PhraseBook *phraseBook) - : pb(phraseBook), ferrorCount(0) { } - - virtual bool startElement(const QString &namespaceURI, - const QString &localName, const QString &qName, - const QXmlAttributes &atts); - virtual bool endElement(const QString &namespaceURI, - const QString &localName, const QString &qName); - virtual bool characters(const QString &ch); - virtual bool fatalError(const QXmlParseException &exception); + QphHandler(PhraseBook *phraseBook, QXmlStreamReader &reader) + : XmlParser(reader), pb(phraseBook), ferrorCount(0) + { + } + ~QphHandler() override = default; QString language() const { return m_language; } QString sourceLanguage() const { return m_sourceLanguage; } private: + bool startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) override; + bool endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) override; + bool characters(const QStringRef &ch) override; + bool fatalError(qint64 line, qint64 column, const QString &message) override; + PhraseBook *pb; QString source; QString target; @@ -133,14 +134,15 @@ private: int ferrorCount; }; -bool QphHandler::startElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName, - const QXmlAttributes &atts) +bool QphHandler::startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) { + Q_UNUSED(namespaceURI) + Q_UNUSED(localName) + if (qName == QLatin1String("QPH")) { - m_language = atts.value(QLatin1String("language")); - m_sourceLanguage = atts.value(QLatin1String("sourcelanguage")); + m_language = atts.value(QLatin1String("language")).toString(); + m_sourceLanguage = atts.value(QLatin1String("sourcelanguage")).toString(); } else if (qName == QLatin1String("phrase")) { source.truncate(0); target.truncate(0); @@ -150,10 +152,12 @@ bool QphHandler::startElement(const QString & /* namespaceURI */, return true; } -bool QphHandler::endElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName) +bool QphHandler::endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) { + Q_UNUSED(namespaceURI) + Q_UNUSED(localName) + if (qName == QLatin1String("source")) source = accum; else if (qName == QLatin1String("target")) @@ -165,20 +169,20 @@ bool QphHandler::endElement(const QString & /* namespaceURI */, return true; } -bool QphHandler::characters(const QString &ch) +bool QphHandler::characters(const QStringRef &ch) { accum += ch; return true; } -bool QphHandler::fatalError(const QXmlParseException &exception) +bool QphHandler::fatalError(qint64 line, qint64 column, const QString &message) { if (ferrorCount++ == 0) { QString msg = PhraseBook::tr("Parse error at line %1, column %2 (%3).") - .arg(exception.lineNumber()).arg(exception.columnNumber()) - .arg(exception.message()); - QMessageBox::information(0, - QObject::tr("Qt Linguist"), msg); + .arg(line) + .arg(column) + .arg(message); + QMessageBox::information(nullptr, QObject::tr("Qt Linguist"), msg); } return false; } @@ -223,20 +227,10 @@ bool PhraseBook::load(const QString &fileName, bool *langGuessed) m_fileName = fileName; - QXmlInputSource in(&f); - QXmlSimpleReader reader; - // don't click on these! - reader.setFeature(QLatin1String("http://xml.org/sax/features/namespaces"), false); - reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true); - reader.setFeature(QLatin1String("http://trolltech.com/xml/features/report-whitespace" - "-only-CharData"), false); - QphHandler *hand = new QphHandler(this); - reader.setContentHandler(hand); - reader.setErrorHandler(hand); - - bool ok = reader.parse(in); - reader.setContentHandler(0); - reader.setErrorHandler(0); + QXmlStreamReader reader(&f); + QphHandler *hand = new QphHandler(this, reader); + reader.setNamespaceProcessing(false); + bool ok = hand->parse(); Translator::languageAndCountry(hand->language(), &m_language, &m_country); *langGuessed = false; diff --git a/src/linguist/lupdate/clangtoolastreader.cpp b/src/linguist/lupdate/clangtoolastreader.cpp index bd3147838..ceff09523 100644 --- a/src/linguist/lupdate/clangtoolastreader.cpp +++ b/src/linguist/lupdate/clangtoolastreader.cpp @@ -25,17 +25,42 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include "clangtoolastreader.h" - -#include <QtCore/qregularexpression.h> -#include <clang/Lex/MacroArgs.h> -#include <clang/Basic/TokenKinds.h> +#include "clangtoolastreader.h" +#include "translator.h" QT_BEGIN_NAMESPACE namespace LupdatePrivate { + /* + Retrieves the context for the NOOP macros using the context of + the NamedDeclaration within which the Macro is. + The context is stripped of the function or method part as it used not to be retrieved + in the previous cpp parser. + */ + QString contextForNoopMacro(clang::NamedDecl *namedDecl) + { + QStringList context; + const clang::DeclContext *decl = namedDecl->getDeclContext(); + while (decl) { + if (clang::isa<clang::NamedDecl>(decl) && !decl->isFunctionOrMethod()) { + if (const auto *namespaceDecl = clang::dyn_cast<clang::NamespaceDecl>(decl)) { + context.prepend(namespaceDecl->isAnonymousNamespace() + ? QStringLiteral("(anonymous namespace)") + : QString::fromStdString(namespaceDecl->getDeclName().getAsString())); + } else if (const auto *recordDecl = clang::dyn_cast<clang::RecordDecl>(decl)) { + static const QString anonymous = QStringLiteral("(anonymous %1)"); + context.prepend(recordDecl->getIdentifier() + ? QString::fromStdString(recordDecl->getDeclName().getAsString()) + : anonymous.arg(QLatin1String(recordDecl->getKindName().data()))); + } + } + decl = decl->getParent(); + } + return context.join(QStringLiteral("::")); + } + QString contextForFunctionDecl(clang::FunctionDecl *func, const std::string &funcName) { std::string context; @@ -50,67 +75,6 @@ namespace LupdatePrivate return QString::fromStdString(context.substr(0, context.find("::" + funcName, 0))); } - enum QuoteCompulsary - { - None = 0x01, - Left = 0x02, // Left quote is mandatory - Right = 0x04, // Right quote is mandatory - LeftAndRight = Left | Right // Both quotes are mandatory - }; - - /* - Removes the quotes around the lupdate extra, ID meta data, magic and - ID prefix comments and source string literals. - Depending on the given compulsory option, quotes can be unbalanced and - still some text is returned. This is to mimic the old lupdate behavior. - */ - QString cleanQuote(llvm::StringRef s, QuoteCompulsary quote) - { - if (s.empty()) - return {}; - s = s.trim(); - if (!s.consume_front("\"") && ((quote & Left) != 0)) - return {}; - if (!s.consume_back("\"") && ((quote & Right) != 0)) - return {}; - return QString::fromStdString(s); - } - - /* - Removes the quotes and a possible existing string literal prefix - for a given string literal coming from the source code. Do not use - to clean the quotes around the lupdate translator specific comments. - */ - QString cleanQuote(const std::string &token) - { - if (token.empty()) - return {}; - - const QString string = QString::fromStdString(token).trimmed(); - const int index = string.indexOf(QLatin1Char('"')); - if (index <= 0) - return LupdatePrivate::cleanQuote(token, QuoteCompulsary::LeftAndRight); - - QRegularExpressionMatch result; - if (string.at(index - 1) == QLatin1Char('R')) { - static const QRegularExpression rawStringLiteral { - QStringLiteral( - "(?:\\bu8|\\b[LuU])??R\\\"([^\\(\\)\\\\ ]{0,16})\\((?<characters>.*)\\)\\1\\\"" - ), QRegularExpression::DotMatchesEverythingOption }; - result = rawStringLiteral.match(string); - } else { - static const QRegularExpression stringLiteral { - QStringLiteral( - "(?:\\bu8|\\b[LuU])+?\\\"(?<characters>[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*)\\\"" - ) - }; - result = stringLiteral.match(string); - } - if (result.hasMatch()) - return result.captured(QStringLiteral("characters")); - return string; - } - static bool capture(const QRegularExpression &exp, const QString &line, QString *i, QString *c) { i->clear(), c->clear(); @@ -168,6 +132,15 @@ namespace LupdatePrivate return true; return false; } + + bool isPointWithin(const clang::SourceRange &sourceRange, const clang::SourceLocation &point, + const clang::SourceManager &sm) + { + clang::SourceLocation start = sourceRange.getBegin(); + clang::SourceLocation end = sourceRange.getEnd(); + return point == start || point == end || (sm.isBeforeInTranslationUnit(start, point) + && sm.isBeforeInTranslationUnit(point, end)); + } } /* @@ -270,7 +243,7 @@ bool LupdateVisitor::VisitCallExpr(clang::CallExpr *callExpression) qCDebug(lcClang) << "Plural : " << store.lupdatePlural; break; } - m_translationStoresFromAST.push_back(store); + m_stores.AST.push_back(store); return true; } @@ -487,227 +460,137 @@ void LupdateVisitor::setInfoFromRawComment(const QString &commentString, } } -/* - Fill the Translator with the retrieved information after traversing the AST. -*/ -void LupdateVisitor::fillTranslator() -{ - for (const auto &store : qAsConst(m_translationStoresFromAST)) - fillTranslator(store); - // Here also need to fill the translator with the information retrieved from the PreProcessor -} - -void LupdateVisitor::fillTranslator(TranslationRelatedStore store) -{ - bool forcePlural = false; - switch (trFunctionAliasManager.trFunctionByName(store.funcName)) { - case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: - // If there is a Q_DECLARE_TR_FUNCTION the context given takes priority - // over the retrieved context. - // The retrieved context for Q_DECLARE_TR_FUNCTION (where the macro was) - // has to fit the start of the retrieved context of the tr function or - // NOOP macro if there is already a argument giving the context, it has - // priority. - //handleDeclareTrFunctions(); // TODO: Implement. - break; - case TrFunctionAliasManager::Function_QT_TR_N_NOOP: - forcePlural = true; - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_tr: - case TrFunctionAliasManager::Function_trUtf8: - case TrFunctionAliasManager::Function_QT_TR_NOOP: - case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8: - handleTr(store, forcePlural); - break; - case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: - case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: - forcePlural = true; - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_translate: - case TrFunctionAliasManager::Function_findMessage: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: - handleTranslate(store, forcePlural); - break; - case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: - forcePlural = true; - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_qtTrId: - case TrFunctionAliasManager::Function_QT_TRID_NOOP: - handleTrId(store, forcePlural); - break; - } -} - -TranslatorMessage LupdateVisitor::fillTranslatorMessage(const TranslationRelatedStore &store, - bool forcePlural, bool isId) -{ - QString context; - if (!isId) { - context = ParserTool::transcode(store.contextArg.isEmpty() ? store.contextRetrieved - : store.contextArg); - } - - TranslatorMessage msg(context, - ParserTool::transcode(isId ? store.lupdateSourceWhenId - : store.lupdateSource), - ParserTool::transcode(store.lupdateComment), - QString(), - store.lupdateLocationFile, - store.lupdateLocationLine, - QStringList(), - TranslatorMessage::Type::Unfinished, - (forcePlural ? forcePlural : !store.lupdatePlural.isEmpty())); - - if (!store.lupdateAllMagicMetaData.empty()) - msg.setExtras(store.lupdateAllMagicMetaData); - msg.setExtraComment(ParserTool::transcode(store.lupdateExtraComment)); - return msg; -} - -void LupdateVisitor::handleTranslate(const TranslationRelatedStore &store, bool forcePlural) -{ - if (!store.lupdateSourceWhenId.isEmpty()) - qCDebug(lcClang) << "//% is ignored when using translate function\n"; - - TranslatorMessage msg = fillTranslatorMessage(store, forcePlural); - msg.setId(ParserTool::transcode(store.lupdateIdMetaData)); // //= NOT to be used with qTrId - m_tor->append(msg); -} - -void LupdateVisitor::handleTr(const TranslationRelatedStore &store, bool forcePlural) -{ - if (!store.lupdateSourceWhenId.isEmpty()) - qCDebug(lcClang) << "//% is ignored when using tr function\n"; - if (store.contextRetrieved.isEmpty() && store.contextArg.isEmpty()) { - qCDebug(lcClang) << "tr() cannot be called without context \n"; - return; - } - - TranslatorMessage msg = fillTranslatorMessage(store, forcePlural); - msg.setId(ParserTool::transcode(store.lupdateIdMetaData)); // //= NOT to be used with qTrId - m_tor->append(msg); -} - -void LupdateVisitor::handleTrId(const TranslationRelatedStore &store, bool forcePlural) -{ - if (!store.lupdateIdMetaData.isEmpty()) - qCDebug(lcClang) << "//= is ignored when using qtTrId function \n"; - - TranslatorMessage msg = fillTranslatorMessage(store, forcePlural, true); - msg.setId(ParserTool::transcode(store.lupdateId)); - m_tor->append(msg); -} - void LupdateVisitor::processPreprocessorCalls() { - for (const auto &store : qAsConst(m_translationStoresFromPP)) + m_macro = (m_stores.Preprocessor.size() > 0); + for (const auto &store : qAsConst(m_stores.Preprocessor)) processPreprocessorCall(store); } void LupdateVisitor::processPreprocessorCall(TranslationRelatedStore store) { - const std::vector<QString> rawComments = rawCommentsFromSourceLocation(store.callLocation); + const std::vector<QString> rawComments = rawCommentsFromSourceLocation(store + .callLocation(m_context->getSourceManager())); for (const auto &rawComment : rawComments) setInfoFromRawComment(rawComment, &store); if (store.isValid()) { if (store.funcName.contains(QStringLiteral("Q_DECLARE_TR_FUNCTIONS"))) - m_qDeclateTrFunctionContext.push_back(store); + m_qDeclareTrMacroAll.push_back(store); else - m_noopTranslationStores.push_back(store); + m_noopTranslationMacroAll.push_back(store); store.printStore(); } } -void LupdatePPCallbacks::MacroExpands(const clang::Token ¯oNameTok, - const clang::MacroDefinition ¯oDefinition, clang::SourceRange range, - const clang::MacroArgs *args) +bool LupdateVisitor::VisitNamedDecl(clang::NamedDecl *namedDeclaration) { - if (!args) - return; - const auto &sm = m_preprocessor.getSourceManager(); - llvm::StringRef fileName = sm.getFilename(range.getBegin()); - if (fileName != m_inputFile) - return; + if (!m_macro) + return true; + auto fullLocation = m_context->getFullLoc(namedDeclaration->getBeginLoc()); + if (!fullLocation.isValid() || !fullLocation.getFileEntry()) + return true; + + if (fullLocation.getFileEntry()->getName() != m_inputFile) + return true; + + qCDebug(lcClang) << "NamedDecl Name: " << namedDeclaration->getQualifiedNameAsString(); + qCDebug(lcClang) << "NamedDecl source: " << namedDeclaration->getSourceRange().printToString( + m_context->getSourceManager()); + // Checks if there is a macro located within the range of this NamedDeclaration + // in order to find a context for the macro + findContextForTranslationStoresFromPP(namedDeclaration); + return true; +} - const QString funcName = QString::fromStdString(m_preprocessor.getSpelling(macroNameTok)); - qCDebug(lcClang) << "func Name " << funcName; - if (!funcName.contains(QStringLiteral("NOOP")) - && !funcName.contains(QStringLiteral("Q_DECLARE_TR_FUNCTIONS"))) { - return; +void LupdateVisitor::findContextForTranslationStoresFromPP(clang::NamedDecl *namedDeclaration) +{ + qCDebug(lcClang) << "=================findContextForTranslationStoresFromPP==================="; + qCDebug(lcClang) << "m_noopTranslationMacroAll " << m_noopTranslationMacroAll.size(); + qCDebug(lcClang) << "m_qDeclareTrMacroAll " << m_qDeclareTrMacroAll.size(); + clang::SourceManager &sm = m_context->getSourceManager(); + + // Looking for NOOP context only in the input file + // because we are not interested in the NOOP from all related file + // Once QT_TR_NOOP are gone this step can be removes because the only + // QT_...NOOP left will have an context as argument + for (TranslationRelatedStore &store : m_noopTranslationMacroAll) { + if (!store.contextArg.isEmpty()) + continue; + clang::SourceLocation sourceLoc = store.callLocation(sm); + if (!sourceLoc.isValid()) + continue; + if (LupdatePrivate::isPointWithin(namedDeclaration->getSourceRange(), sourceLoc, sm)) { + /* + void N3::C1::C12::C121::f2() + { + const char test_NOOP[] = QT_TR_NOOP("A QT_TR_NOOP N3::C1::C13"); + } + In such case namedDeclaration->getQualifiedNameAsString() will give only + test_NOOP as context. + This is why the following function is needed + */ + store.contextRetrievedTempNOOP = LupdatePrivate::contextForNoopMacro(namedDeclaration); + qCDebug(lcClang) << "------------------------------------------NOOP Macro in range ---"; + qCDebug(lcClang) << "Range " << namedDeclaration->getSourceRange().printToString(sm); + qCDebug(lcClang) << "Point " << sourceLoc.printToString(sm); + qCDebug(lcClang) << "=========== Visit Named Declaration ============================="; + qCDebug(lcClang) << " Declaration Location " << + namedDeclaration->getSourceRange().printToString(sm); + qCDebug(lcClang) << " Macro Location " + << sourceLoc.printToString(sm); + qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() " + << namedDeclaration->getQualifiedNameAsString(); + qCDebug(lcClang) << " Context LupdatePrivate::contextForNoopMacro " + << store.contextRetrievedTempNOOP; + qCDebug(lcClang) << " Context Retrieved " << store.contextRetrievedTempNOOP; + qCDebug(lcClang) << "================================================================="; + store.printStore(); + } } - TranslationRelatedStore store; - store.callType = QStringLiteral("MacroExpands"); - store.funcName = funcName; - store.lupdateLocationFile = QString::fromStdString(fileName); - store.lupdateLocationLine = sm.getExpansionLineNumber(range.getBegin()); - store.locationCol = sm.getExpansionColumnNumber(range.getBegin()); - store.callLocation = range.getBegin(); - - std::vector<QString> arguments(args->getNumMacroArguments()); - for (unsigned i = 0; i < args->getNumMacroArguments(); i++) { - auto preExpArguments = const_cast<clang::MacroArgs*>(args)->getPreExpArgument(i, - m_preprocessor); - QString temp; - for (const auto &preExpArgument : preExpArguments) { - const auto kind = preExpArgument.getKind(); - if (kind == clang::tok::TokenKind::identifier) - temp = QString::fromStdString(m_preprocessor.getSpelling(preExpArgument)); - else if (clang::tok::isStringLiteral(kind)) - temp += LupdatePrivate::cleanQuote(m_preprocessor.getSpelling(preExpArgument)); + for (TranslationRelatedStore &store : m_qDeclareTrMacroAll) { + clang::SourceLocation sourceLoc = store.callLocation(sm); + if (!sourceLoc.isValid()) + continue; + if (LupdatePrivate::isPointWithin(namedDeclaration->getSourceRange(), sourceLoc, sm)) { + store.contextRetrieved = QString::fromStdString( + namedDeclaration->getQualifiedNameAsString()); + qCDebug(lcClang) << "------------------------------------------DECL Macro in range ---"; + qCDebug(lcClang) << "Range " << namedDeclaration->getSourceRange().printToString(sm); + qCDebug(lcClang) << "Point " << sourceLoc.printToString(sm); + qCDebug(lcClang) << "=========== Visit Named Declaration ============================="; + qCDebug(lcClang) << " Declaration Location " << + namedDeclaration->getSourceRange().printToString(sm); + qCDebug(lcClang) << " Macro Location " + << sourceLoc.printToString(sm); + qCDebug(lcClang) << " Context namedDeclaration->getQualifiedNameAsString() " + << store.contextRetrieved; + qCDebug(lcClang) << " Context LupdatePrivate::contextForNoopMacro " + << LupdatePrivate::contextForNoopMacro(namedDeclaration); + qCDebug(lcClang) << " Context Retrieved " << store.contextRetrieved; + qCDebug(lcClang) << "================================================================="; + store.printStore(); } - arguments[i] = temp; } - storeMacroArguments(arguments, &store); - if (store.isValid()) - m_translationStores.push_back(store); } -void LupdatePPCallbacks::storeMacroArguments(const std::vector<QString> &args, - TranslationRelatedStore *store) +void LupdateVisitor::generateOuput() { - switch (trFunctionAliasManager.trFunctionByName(store->funcName)) { - // only one argument: the context with no " - case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: - if (args.size() != 1) - break; - store->contextArg = args[0]; - break; - // only one argument: the source - case TrFunctionAliasManager::Function_QT_TR_N_NOOP: - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_QT_TR_NOOP: - case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8: - if (args.size() != 1) - break; - store->lupdateSource = args[0]; - break; - // two arguments: the context and the source - case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: - case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: - case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: - if (args.size() != 2) - break; - store->contextArg = args[0]; - store->lupdateSource = args[1]; - break; - // only one argument (?) the message Id - case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: - Q_FALLTHROUGH(); - case TrFunctionAliasManager::Function_qtTrId: - case TrFunctionAliasManager::Function_QT_TRID_NOOP: - if (args.size() != 1) - break; - store->lupdateId = args[0]; - break; + qCDebug(lcClang) << "===================generateOuput============================"; + + for (TranslationRelatedStore &store : m_noopTranslationMacroAll) { + if (store.contextRetrievedTempNOOP.isEmpty() && store.contextArg.isEmpty()) + continue; + // only fill if a context has been retrieved in the file we're currently visiting + m_stores.QNoopTranlsationWithContext.push_back(store); + } + + for (TranslationRelatedStore &store : m_qDeclareTrMacroAll) { + if (store.contextRetrieved.isEmpty()) + continue; + // only fill if a context has been retrieved in the file we're currently visiting + m_stores.QDeclareTrWithContext.push_back(store); } } diff --git a/src/linguist/lupdate/clangtoolastreader.h b/src/linguist/lupdate/clangtoolastreader.h index d7f6fce3b..8f9a54565 100644 --- a/src/linguist/lupdate/clangtoolastreader.h +++ b/src/linguist/lupdate/clangtoolastreader.h @@ -29,148 +29,47 @@ #ifndef CLANG_TOOL_AST_READER_H #define CLANG_TOOL_AST_READER_H -#include "lupdate.h" -#include "translator.h" -#include "translatormessage.h" - -#include <QtCore/qloggingcategory.h> +#include "cpp_clang.h" #if defined(Q_CC_MSVC) +# pragma warning(push) # pragma warning(disable: 4100) # pragma warning(disable: 4146) # pragma warning(disable: 4267) # pragma warning(disable: 4624) #endif -#include <llvm/ADT/APInt.h> #include <clang/AST/RecursiveASTVisitor.h> #include <clang/Frontend/CompilerInstance.h> #include <clang/Frontend/FrontendActions.h> #include <clang/Tooling/Tooling.h> -#include <clang/Lex/PPCallbacks.h> -#include <clang/Lex/Preprocessor.h> #if defined(Q_CC_MSVC) -# pragma warning(default: 4100) -# pragma warning(default: 4146) -# pragma warning(default: 4267) -# pragma warning(default: 4624) +# pragma warning(pop) #endif #include <memory> -#include <string> -#include <utility> -#include <vector> QT_BEGIN_NAMESPACE -inline QDebug operator<<(QDebug out, const std::string& str) -{ - out << QString::fromStdString(str); - return out; -} -Q_DECLARE_LOGGING_CATEGORY(lcClang) - -#define LUPDATE_CLANG_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch)) -#define LUPDATE_CLANG_VERSION LUPDATE_CLANG_VERSION_CHECK(LUPDATE_CLANG_VERSION_MAJOR, \ - LUPDATE_CLANG_VERSION_MINOR, LUPDATE_CLANG_VERSION_PATCH) - -// Local storage of translation information (information from the AST and -// linguist side) -struct TranslationRelatedStore -{ - QString callType; - QString rawCode; - QString funcName; - qint64 locationCol = -1; - QString contextArg; - QString contextRetrieved; - QString lupdateSource; - QString lupdateLocationFile; - qint64 lupdateLocationLine = -1; - QString lupdateId; - QString lupdateSourceWhenId; - QString lupdateIdMetaData; - QString lupdateMagicMetaData; - QHash<QString, QString> lupdateAllMagicMetaData; - QString lupdateComment; - QString lupdateExtraComment; - QString lupdatePlural; - clang::SourceLocation callLocation; - - bool isValid() const - { - return !lupdateLocationFile.isEmpty() && (lupdateLocationLine > -1) && (locationCol > -1); - } - - void printStore() const - { - qCDebug(lcClang) << "------------------ Printing Store----------------------------------\n"; - qCDebug(lcClang) - << "callType : " << callType << "\n" - << "rawCode : \n" << rawCode << "\n" - << "funcName : " << funcName << "\n" - << "LocationCol : " << locationCol << "\n" - << "contextArg : " << contextArg << "\n" - << "contextRetrieved : " << contextRetrieved << "\n" - << "lupdateSource : " << lupdateSource << "\n" - << "lupdateLocationFile : " << lupdateLocationFile << "\n" - << "lupdateLocationLine : " << lupdateLocationLine << "\n" - << "lupdateId : " << lupdateId << "\n" - << "lupdateIdMetaData : " << lupdateIdMetaData << "\n" - << "lupdateMagicMetaData: " << lupdateMagicMetaData << "\n" - << "lupdateComment : " << lupdateComment << "\n" - << "lupdateExtraComment : " << lupdateExtraComment << "\n" - << "lupdatePlural : " << lupdatePlural; - qCDebug(lcClang) << "-------------------------------------------------------------------\n"; - } -}; - -using TranslationStores = std::vector<TranslationRelatedStore>; - -class LupdatePPCallbacks : public clang::PPCallbacks -{ -public: - LupdatePPCallbacks(TranslationStores &translationStores, clang::Preprocessor &preprocessor) - : m_translationStores(translationStores), - m_preprocessor(preprocessor) - { - const auto &sm = m_preprocessor.getSourceManager(); - m_inputFile = sm.getFileEntryForID(sm.getMainFileID())->getName(); - } - - ~LupdatePPCallbacks() override - {} - - // Overridden callback functions. - void MacroExpands(const clang::Token ¯oNameTok, - const clang::MacroDefinition ¯oDefinition, clang::SourceRange range, - const clang::MacroArgs *args) override; - -private: - void storeMacroArguments(const std::vector<QString> &args, TranslationRelatedStore *store); - - TranslationStores &m_translationStores; - clang::Preprocessor &m_preprocessor; - std::string m_inputFile; -}; +class Translator; class LupdateVisitor : public clang::RecursiveASTVisitor<LupdateVisitor> { - friend class LupdateASTConsumer; - public: - explicit LupdateVisitor(clang::ASTContext *context, Translator *tor) - : m_context(context), - m_tor(tor) + explicit LupdateVisitor(clang::ASTContext *context, Stores &stores) + : m_context(context) + , m_stores(stores) { m_inputFile = m_context->getSourceManager().getFileEntryForID( m_context->getSourceManager().getMainFileID())->getName(); } bool VisitCallExpr(clang::CallExpr *callExpression); - void fillTranslator(); void processPreprocessorCalls(); + bool VisitNamedDecl(clang::NamedDecl *namedDeclaration); + void findContextForTranslationStoresFromPP(clang::NamedDecl *namedDeclaration); + void generateOuput(); private: std::vector<QString> rawCommentsForCallExpr(const clang::CallExpr *callExpr) const; @@ -178,30 +77,24 @@ private: void setInfoFromRawComment(const QString &commentString, TranslationRelatedStore *store); - void fillTranslator(TranslationRelatedStore store); - TranslatorMessage fillTranslatorMessage(const TranslationRelatedStore &store, - bool forcePlural, bool isID = false); - void handleTr(const TranslationRelatedStore &store, bool forcePlural); - void handleTrId(const TranslationRelatedStore &store, bool forcePlural); - void handleTranslate(const TranslationRelatedStore &store, bool forcePlural); - void processPreprocessorCall(TranslationRelatedStore store); clang::ASTContext *m_context { nullptr }; Translator *m_tor { nullptr }; std::string m_inputFile; - TranslationStores m_translationStoresFromAST; - TranslationStores m_qDeclateTrFunctionContext; - TranslationStores m_noopTranslationStores; - TranslationStores m_translationStoresFromPP; + Stores &m_stores; + + TranslationStores m_qDeclareTrMacroAll; + TranslationStores m_noopTranslationMacroAll; + bool m_macro = false; }; class LupdateASTConsumer : public clang::ASTConsumer { public: - explicit LupdateASTConsumer(clang::ASTContext *context, Translator *tor) - : m_visitor(context, tor) + explicit LupdateASTConsumer(clang::ASTContext *context, Stores &stores) + : m_visitor(context, stores) {} // This method is called when the ASTs for entire translation unit have been @@ -211,12 +104,7 @@ public: m_visitor.processPreprocessorCalls(); bool traverse = m_visitor.TraverseAST(context); qCDebug(lcClang) << "TraverseAST: " << traverse; - m_visitor.fillTranslator(); - } - - TranslationStores &preprocessorStores() - { - return m_visitor.m_translationStoresFromPP; + m_visitor.generateOuput(); } private: @@ -226,47 +114,42 @@ private: class LupdateFrontendAction : public clang::ASTFrontendAction { public: - LupdateFrontendAction(Translator *tor) - : m_tor(tor) + LupdateFrontendAction(Stores &outputStoresWithContext) + : m_stores(outputStoresWithContext) {} std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( clang::CompilerInstance &compiler, llvm::StringRef /* inFile */) override { - LupdateASTConsumer *consumer = new LupdateASTConsumer(&compiler.getASTContext(), m_tor); - clang::Preprocessor &preprocessor = compiler.getPreprocessor(); - LupdatePPCallbacks *callbacks = new LupdatePPCallbacks(consumer->preprocessorStores(), - preprocessor); - preprocessor.addPPCallbacks(std::unique_ptr<clang::PPCallbacks>(callbacks)); - + auto consumer = new LupdateASTConsumer(&compiler.getASTContext(), m_stores); return std::unique_ptr<clang::ASTConsumer>(consumer); } private: - Translator *m_tor { nullptr }; + Stores &m_stores; }; class LupdateToolActionFactory : public clang::tooling::FrontendActionFactory { public: - LupdateToolActionFactory(Translator *tor) - : m_tor(tor) + LupdateToolActionFactory(Stores &stores) + : m_stores(stores) {} #if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0)) std::unique_ptr<clang::FrontendAction> create() override { - return std::make_unique<LupdateFrontendAction>(m_tor); + return std::make_unique<LupdateFrontendAction>(m_stores); } #else clang::FrontendAction *create() override { - return new LupdateFrontendAction(m_tor); + return new LupdateFrontendAction(m_stores); } #endif private: - Translator *m_tor { nullptr }; + Stores &m_stores; }; QT_END_NAMESPACE diff --git a/src/linguist/lupdate/cpp_clang.cpp b/src/linguist/lupdate/cpp_clang.cpp index 598ac94ca..fc11253bd 100644 --- a/src/linguist/lupdate/cpp_clang.cpp +++ b/src/linguist/lupdate/cpp_clang.cpp @@ -25,9 +25,11 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include "cpp_clang.h" -#include <translator.h> +#include "cpp_clang.h" +#include "clangtoolastreader.h" +#include "lupdatepreprocessoraction.h" +#include "translator.h" #include <clang/Tooling/CommonOptionsParser.h> #include <llvm/Option/Option.h> @@ -66,8 +68,6 @@ void ClangCppParser::loadCPP(Translator &translator, const QStringList &filename for (const QString &filename : filenames) sources.push_back(filename.toStdString()); - // The ClangTool is to be created and run from this function. - int argc = 4; // NEED 2 empty one to start!!! otherwise: LLVM::ERROR const QByteArray jsonPath = cd.m_compileCommandsPath.toLocal8Bit(); @@ -77,11 +77,12 @@ void ClangCppParser::loadCPP(Translator &translator, const QStringList &filename clang::tooling::ClangTool tool(OptionsParser.getCompilations(), sources); tool.appendArgumentsAdjuster(getClangArgumentAdjuster()); - Translator *tor = new Translator(); + Stores stores; + tool.run(new LupdatePreprocessorActionFactory(stores.Preprocessor)); + tool.run(new LupdateToolActionFactory(stores)); - // A ClangTool needs a new FrontendAction for each translation unit it runs on - // A Customized FrontendActionFactory is building a customized FrondendAction - tool.run(new LupdateToolActionFactory(tor)); + Translator *tor = new Translator(); + ClangCppParser::fillTranslator(tor, stores); if (QLoggingCategory("qt.lupdate.clang").isDebugEnabled()) tor->dump(); @@ -90,4 +91,170 @@ void ClangCppParser::loadCPP(Translator &translator, const QStringList &filename translator.extend(msg, cd); } +/* + Fill the Translator with the retrieved information after traversing the AST. +*/ +void ClangCppParser::fillTranslator(Translator *tor, Stores &stores) +{ + correctAstTranslationContext(stores); + for (auto &store : stores.AST) + fillTranslator(tor, store); + + correctNoopTanslationContext(stores); + for (auto &store : stores.QNoopTranlsationWithContext) + fillTranslator(tor, store); +} + +void ClangCppParser::fillTranslator(Translator *tor, TranslationRelatedStore store) +{ + bool plural = false; + switch (trFunctionAliasManager.trFunctionByName(store.funcName)) { + case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: + break; + case TrFunctionAliasManager::Function_QT_TR_N_NOOP: + plural = true; + Q_FALLTHROUGH(); + case TrFunctionAliasManager::Function_tr: + case TrFunctionAliasManager::Function_trUtf8: + case TrFunctionAliasManager::Function_QT_TR_NOOP: + case TrFunctionAliasManager::Function_QT_TR_NOOP_UTF8: + handleTr(tor, store, plural); + break; + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: + plural = true; + Q_FALLTHROUGH(); + case TrFunctionAliasManager::Function_translate: + case TrFunctionAliasManager::Function_findMessage: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: + handleTranslate(tor, store, plural); + break; + case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: + plural = true; + Q_FALLTHROUGH(); + case TrFunctionAliasManager::Function_qtTrId: + case TrFunctionAliasManager::Function_QT_TRID_NOOP: + handleTrId(tor, store, plural); + break; + } +} + +TranslatorMessage ClangCppParser::fillTranslatorMessage(const TranslationRelatedStore &store, + const QString &id, bool plural, bool isId) +{ + QString context; + if (!isId) { + context = ParserTool::transcode(store.contextArg.isEmpty() ? store.contextRetrieved + : store.contextArg); + } + + TranslatorMessage msg(context, + ParserTool::transcode(isId ? store.lupdateSourceWhenId + : store.lupdateSource), + ParserTool::transcode(store.lupdateComment), + QString(), + store.lupdateLocationFile, + store.lupdateLocationLine, + QStringList(), + TranslatorMessage::Type::Unfinished, + (plural ? plural : !store.lupdatePlural.isEmpty())); + + if (!store.lupdateAllMagicMetaData.empty()) + msg.setExtras(store.lupdateAllMagicMetaData); + msg.setExtraComment(ParserTool::transcode(store.lupdateExtraComment)); + msg.setId(ParserTool::transcode(id)); + return msg; +} + +void ClangCppParser::handleTranslate(Translator *tor, const TranslationRelatedStore &store, + bool plural) +{ + if (!store.lupdateSourceWhenId.isEmpty()) + qCDebug(lcClang) << "//% is ignored when using translate function\n"; + tor->append(fillTranslatorMessage(store, store.lupdateIdMetaData, plural, false)); +} + +void ClangCppParser::handleTr(Translator *tor, const TranslationRelatedStore &store, bool plural) +{ + if (!store.lupdateSourceWhenId.isEmpty()) + qCDebug(lcClang) << "//% is ignored when using tr function\n"; + if (store.contextRetrieved.isEmpty() && store.contextArg.isEmpty()) { + qCDebug(lcClang) << "tr() cannot be called without context \n"; + return; + } + tor->append(fillTranslatorMessage(store, store.lupdateIdMetaData, plural, false)); +} + +void ClangCppParser::handleTrId(Translator *tor, const TranslationRelatedStore &store, bool plural) +{ + if (!store.lupdateIdMetaData.isEmpty()) + qCDebug(lcClang) << "//= is ignored when using qtTrId function \n"; + tor->append(fillTranslatorMessage(store, store.lupdateId, plural, true)); +} + +void ClangCppParser::correctAstTranslationContext(Stores &stores) +{ + for (auto &store : stores.AST) { + if (!store.contextArg.isEmpty()) + continue; + + // If there is a Q_DECLARE_TR_FUNCTION the context given there takes + // priority over the retrieved context. The retrieved context for + // Q_DECLARE_TR_FUNCTION (where the macro was) has to fit the retrieved + // context of the tr function if there is already a argument giving the + // context, it has priority + for (auto &declareStore : stores.QDeclareTrWithContext) { + qCDebug(lcClang) << "----------------------------"; + qCDebug(lcClang) << "Tr call context retrieved " << store.contextRetrieved; + qCDebug(lcClang) << "Tr call source " << store.lupdateSource; + qCDebug(lcClang) << "- DECLARE context retrieved " << declareStore.contextRetrieved; + qCDebug(lcClang) << "- DECLARE context Arg " << declareStore.contextArg; + if (declareStore.contextRetrieved.isEmpty()) + continue; + if (!declareStore.contextRetrieved.startsWith(store.contextRetrieved)) + continue; + if (store.contextRetrieved.size() == declareStore.contextRetrieved.size()) { + qCDebug(lcClang) << "* Tr call context retrieved " << store.contextRetrieved; + qCDebug(lcClang) << "* DECLARE context retrieved " << declareStore.contextRetrieved; + qCDebug(lcClang) << "* DECLARE context Arg " << declareStore.contextArg; + store.contextArg = declareStore.contextArg; + } + } + } +} + +void ClangCppParser::correctNoopTanslationContext(Stores &stores) +{ + for (auto &store : stores.QNoopTranlsationWithContext) { + if (!store.contextArg.isEmpty()) + continue; + qCDebug(lcClang) << "----------------------------"; + qCDebug(lcClang) << "NOOP call context retrieved Temp" << store.contextRetrievedTempNOOP; + qCDebug(lcClang) << "NOOP call source " << store.lupdateSource; + + for (const auto &qDeclare : stores.QDeclareTrWithContext) { + bool firstCheck = false; + bool secondCheck = false; + qCDebug(lcClang) << "- DECLARE context retrieved " << qDeclare.contextRetrieved; + qCDebug(lcClang) << "- DECLARE context Arg " << qDeclare.contextArg; + if (store.contextRetrievedTempNOOP.startsWith(qDeclare.contextRetrieved)) { + firstCheck = (store.contextRetrievedTempNOOP.size() == qDeclare.contextRetrieved.size() + || (store.contextRetrievedTempNOOP.at(qDeclare.contextRetrieved.size() + 1) + == QLatin1Char(':'))); + secondCheck = qDeclare.contextRetrieved.size() > store.contextRetrieved.size(); + if (firstCheck && secondCheck) { + store.contextRetrieved = qDeclare.contextRetrieved; + store.contextArg = qDeclare.contextArg; + qCDebug(lcClang) << "* NOOP call context retrieved " << store.contextRetrieved; + qCDebug(lcClang) << "* DECLARE context retrieved " << qDeclare.contextRetrieved; + qCDebug(lcClang) << "* DECLARE context Arg " << qDeclare.contextArg; + } + } + } + } +} + QT_END_NAMESPACE diff --git a/src/linguist/lupdate/cpp_clang.h b/src/linguist/lupdate/cpp_clang.h index 16e03fb70..eabf2d830 100644 --- a/src/linguist/lupdate/cpp_clang.h +++ b/src/linguist/lupdate/cpp_clang.h @@ -29,16 +29,199 @@ #ifndef CLANG_CPP_H #define CLANG_CPP_H -#include "clangtoolastreader.h" #include "lupdate.h" +#include <QtCore/qloggingcategory.h> +#include <QtCore/qregularexpression.h> + +#if defined(Q_CC_MSVC) +# pragma warning(push) +# pragma warning(disable: 4100) +# pragma warning(disable: 4146) +# pragma warning(disable: 4267) +# pragma warning(disable: 4624) +#endif + +#include <clang/Basic/SourceLocation.h> +#include <clang/Basic/SourceManager.h> + +#if defined(Q_CC_MSVC) +# pragma warning(pop) +#endif + +#include <vector> + QT_BEGIN_NAMESPACE -namespace ClangCppParser { +inline QDebug operator<<(QDebug out, const std::string& str) +{ + out << QString::fromStdString(str); + return out; +} +Q_DECLARE_LOGGING_CATEGORY(lcClang) + +#define LUPDATE_CLANG_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch)) +#define LUPDATE_CLANG_VERSION LUPDATE_CLANG_VERSION_CHECK(LUPDATE_CLANG_VERSION_MAJOR, \ + LUPDATE_CLANG_VERSION_MINOR, LUPDATE_CLANG_VERSION_PATCH) + +// Local storage of translation information (information from the AST and linguist side) +struct TranslationRelatedStore +{ + QString callType; + QString rawCode; + QString funcName; + qint64 locationCol = -1; + QString contextArg; + QString contextRetrieved; + QString contextRetrievedTempNOOP; + QString lupdateSource; + QString lupdateLocationFile; + qint64 lupdateLocationLine = -1; + QString lupdateId; + QString lupdateSourceWhenId; + QString lupdateIdMetaData; + QString lupdateMagicMetaData; + QHash<QString, QString> lupdateAllMagicMetaData; + QString lupdateComment; + QString lupdateExtraComment; + QString lupdatePlural; + clang::SourceLocation sourceLocation; + + bool isValid() const + { + return !lupdateLocationFile.isEmpty() && (lupdateLocationLine > -1) && (locationCol > -1); + } + + clang::SourceLocation callLocation(const clang::SourceManager &sourceManager) + { + if (sourceLocation.isInvalid()) { + auto sourceFile = sourceManager.getFileManager() + .getFile(lupdateLocationFile.toStdString()); +#if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0)) + sourceLocation = sourceManager.translateFileLineCol(sourceFile.get(), + lupdateLocationLine, locationCol); +#else + sourceLocation = sourceManager.translateFileLineCol(sourceFile, lupdateLocationLine, + locationCol); +#endif + } + return sourceLocation; + } + + void printStore() const + { + qCDebug(lcClang) << "------------------ Printing Store----------------------------------\n"; + qCDebug(lcClang) + << "callType : " << callType << "\n" + << "rawCode : \n" << rawCode << "\n" + << "funcName : " << funcName << "\n" + << "LocationCol : " << locationCol << "\n" + << "contextArg : " << contextArg << "\n" + << "contextRetrieved : " << contextRetrieved << "\n" + << "lupdateSource : " << lupdateSource << "\n" + << "lupdateLocationFile : " << lupdateLocationFile << "\n" + << "lupdateLocationLine : " << lupdateLocationLine << "\n" + << "lupdateId : " << lupdateId << "\n" + << "lupdateIdMetaData : " << lupdateIdMetaData << "\n" + << "lupdateMagicMetaData: " << lupdateMagicMetaData << "\n" + << "lupdateComment : " << lupdateComment << "\n" + << "lupdateExtraComment : " << lupdateExtraComment << "\n" + << "lupdatePlural : " << lupdatePlural; + qCDebug(lcClang) << "-------------------------------------------------------------------\n"; + } +}; +using TranslationStores = std::vector<TranslationRelatedStore>; + +struct Stores +{ + TranslationStores Preprocessor; + TranslationStores AST; + TranslationStores QDeclareTrWithContext; + TranslationStores QNoopTranlsationWithContext; +}; + +namespace LupdatePrivate +{ + enum QuoteCompulsary + { + None = 0x01, + Left = 0x02, // Left quote is mandatory + Right = 0x04, // Right quote is mandatory + LeftAndRight = Left | Right // Both quotes are mandatory + }; + + /* + Removes the quotes around the lupdate extra, ID meta data, magic and + ID prefix comments and source string literals. + Depending on the given compulsory option, quotes can be unbalanced and + still some text is returned. This is to mimic the old lupdate behavior. + */ + static QString cleanQuote(llvm::StringRef s, QuoteCompulsary quote) + { + if (s.empty()) + return {}; + s = s.trim(); + if (!s.consume_front("\"") && ((quote & Left) != 0)) + return {}; + if (!s.consume_back("\"") && ((quote & Right) != 0)) + return {}; + return QString::fromStdString(s); + } + + /* + Removes the quotes and a possible existing string literal prefix + for a given string literal coming from the source code. Do not use + to clean the quotes around the lupdate translator specific comments. + */ + static QString cleanQuote(const std::string &token) + { + if (token.empty()) + return {}; + + const QString string = QString::fromStdString(token).trimmed(); + const int index = string.indexOf(QLatin1Char('"')); + if (index <= 0) + return LupdatePrivate::cleanQuote(token, QuoteCompulsary::LeftAndRight); + + QRegularExpressionMatch result; + if (string.at(index - 1) == QLatin1Char('R')) { + static const QRegularExpression rawStringLiteral { + QStringLiteral( + "(?:\\bu8|\\b[LuU])??R\\\"([^\\(\\)\\\\ ]{0,16})\\((?<characters>.*)\\)\\1\\\"" + ), QRegularExpression::DotMatchesEverythingOption }; + result = rawStringLiteral.match(string); + } else { + static const QRegularExpression stringLiteral { + QStringLiteral( + "(?:\\bu8|\\b[LuU])+?\\\"(?<characters>[^\\\"\\\\]*(?:\\\\.[^\\\"\\\\]*)*)\\\"" + ) + }; + result = stringLiteral.match(string); + } + if (result.hasMatch()) + return result.captured(QStringLiteral("characters")); + return string; + } +} + +namespace ClangCppParser +{ void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd); + + void fillTranslator(Translator *tor, Stores &stores); + void fillTranslator(Translator *tor, TranslationRelatedStore store); + + TranslatorMessage fillTranslatorMessage(const TranslationRelatedStore &store, + const QString &id, bool plural, bool isID); + + void handleTr(Translator *tor, const TranslationRelatedStore &store, bool plural); + void handleTrId(Translator *tor, const TranslationRelatedStore &store, bool plural); + void handleTranslate(Translator *tor, const TranslationRelatedStore &store, bool plural); + + void correctAstTranslationContext(Stores &stores); + void correctNoopTanslationContext(Stores &stores); } QT_END_NAMESPACE - #endif diff --git a/src/linguist/lupdate/lupdate.pro b/src/linguist/lupdate/lupdate.pro index 8f1826eee..c653a4e46 100644 --- a/src/linguist/lupdate/lupdate.pro +++ b/src/linguist/lupdate/lupdate.pro @@ -53,10 +53,12 @@ HEADERS += \ qtConfig(clangcpp) { SOURCES += \ cpp_clang.cpp \ - clangtoolastreader.cpp + clangtoolastreader.cpp \ + lupdatepreprocessoraction.cpp HEADERS += \ cpp_clang.h \ - clangtoolastreader.h + clangtoolastreader.h \ + lupdatepreprocessoraction.h } mingw { diff --git a/src/linguist/lupdate/lupdatepreprocessoraction.cpp b/src/linguist/lupdate/lupdatepreprocessoraction.cpp new file mode 100644 index 000000000..feba49942 --- /dev/null +++ b/src/linguist/lupdate/lupdatepreprocessoraction.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "lupdatepreprocessoraction.h" + +#include <clang/Lex/MacroArgs.h> +#include <clang/Basic/TokenKinds.h> + +QT_BEGIN_NAMESPACE + +void LupdatePPCallbacks::MacroExpands(const clang::Token &token, + const clang::MacroDefinition ¯oDefinition, clang::SourceRange sourceRange, + const clang::MacroArgs *macroArgs) +{ + const auto &sm = m_preprocessor.getSourceManager(); + llvm::StringRef fileName = sm.getFilename(sourceRange.getBegin()); + if (fileName != m_inputFile) + return; + + const QString funcName = QString::fromStdString(m_preprocessor.getSpelling(token)); + switch (trFunctionAliasManager.trFunctionByName(funcName)) { + default: + return; + case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: + case TrFunctionAliasManager::Function_QT_TRID_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: + qCDebug(lcClang) << "MacroExpands: Function name:" << funcName; + break; + } + + TranslationRelatedStore store; + store.callType = QStringLiteral("MacroExpands"); + store.funcName = funcName; + store.lupdateLocationFile = QString::fromStdString(fileName); + store.lupdateLocationLine = sm.getExpansionLineNumber(sourceRange.getBegin()); + store.locationCol = sm.getExpansionColumnNumber(sourceRange.getBegin()); + + if (macroArgs) { + std::vector<QString> arguments(macroArgs->getNumMacroArguments()); + for (unsigned i = 0; i < macroArgs->getNumMacroArguments(); i++) { + auto preExpArguments = const_cast<clang::MacroArgs*>(macroArgs)->getPreExpArgument(i, + m_preprocessor); + QString temp; + bool errorArgument = false; + for (const auto &preExpArgument : preExpArguments) { + const auto kind = preExpArgument.getKind(); + switch (trFunctionAliasManager.trFunctionByName(funcName)) { + default: + break; + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: + case TrFunctionAliasManager::Function_QT_TRID_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: + if (!clang::tok::isStringLiteral(kind)) + errorArgument = true; + break; + } + if (errorArgument) + break; + if (clang::tok::isStringLiteral(kind)) + temp += LupdatePrivate::cleanQuote(m_preprocessor.getSpelling(preExpArgument)); + else + temp += QString::fromStdString(m_preprocessor.getSpelling(preExpArgument)); + } + arguments[i] = temp; + } + storeMacroArguments(arguments, &store); + } + if (store.isValid()) + m_stores.push_back(store); +} + +void LupdatePPCallbacks::storeMacroArguments(const std::vector<QString> &args, + TranslationRelatedStore *store) +{ + switch (trFunctionAliasManager.trFunctionByName(store->funcName)) { + // only one argument: the context with no " + case TrFunctionAliasManager::Function_Q_DECLARE_TR_FUNCTIONS: + if (args.size() == 1) + store->contextArg = args[0]; + break; + // two arguments: the context and the source + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_N_NOOP3: + Q_FALLTHROUGH(); + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP_UTF8: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3: + case TrFunctionAliasManager::Function_QT_TRANSLATE_NOOP3_UTF8: + if (args.size() >= 2) { + store->contextArg = args[0]; + store->lupdateSource = args[1]; + } + if (args.size() == 3) + store->lupdateComment = args[2]; + break; + // only one argument (?) the message Id + case TrFunctionAliasManager::Function_QT_TRID_N_NOOP: + Q_FALLTHROUGH(); + case TrFunctionAliasManager::Function_qtTrId: + case TrFunctionAliasManager::Function_QT_TRID_NOOP: + if (args.size() == 1) + store->lupdateId = args[0]; + break; + } +} + +QT_END_NAMESPACE diff --git a/src/linguist/lupdate/lupdatepreprocessoraction.h b/src/linguist/lupdate/lupdatepreprocessoraction.h new file mode 100644 index 000000000..21c7baee1 --- /dev/null +++ b/src/linguist/lupdate/lupdatepreprocessoraction.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LUPDATEPREPROCESSORACTION_H +#define LUPDATEPREPROCESSORACTION_H + +#include "cpp_clang.h" + +#if defined(Q_CC_MSVC) +# pragma warning(push) +# pragma warning(disable: 4100) +# pragma warning(disable: 4146) +# pragma warning(disable: 4267) +# pragma warning(disable: 4624) +#endif + +#include <clang/Frontend/CompilerInstance.h> +#include <clang/Frontend/FrontendActions.h> +#include <clang/Tooling/Tooling.h> +#include <clang/Lex/PPCallbacks.h> +#include <clang/Lex/Preprocessor.h> + +#if defined(Q_CC_MSVC) +# pragma warning(pop) +#endif + +#include <memory> + +QT_BEGIN_NAMESPACE + +class LupdatePPCallbacks : public clang::PPCallbacks +{ +public: + LupdatePPCallbacks(TranslationStores &stores, clang::Preprocessor &pp) + : m_stores(stores) + , m_preprocessor(pp) + { + const auto &sm = m_preprocessor.getSourceManager(); + m_inputFile = sm.getFileEntryForID(sm.getMainFileID())->getName(); + } + +private: + void MacroExpands(const clang::Token &token, const clang::MacroDefinition ¯oDefinition, + clang::SourceRange sourceRange, const clang::MacroArgs *macroArgs) override; + + void storeMacroArguments(const std::vector<QString> &args, TranslationRelatedStore *store); + + std::string m_inputFile; + TranslationStores &m_stores; + clang::Preprocessor &m_preprocessor; +}; + +class LupdatePreprocessorAction : public clang::PreprocessOnlyAction +{ +public: + LupdatePreprocessorAction(TranslationStores &stores) + : m_stores(stores) + {} + +private: + void ExecuteAction() override + { + auto &preprocessor = getCompilerInstance().getPreprocessor(); + preprocessor.SetSuppressIncludeNotFoundError(true); + auto callbacks = new LupdatePPCallbacks(m_stores, preprocessor); + preprocessor.addPPCallbacks(std::unique_ptr<clang::PPCallbacks>(callbacks)); + + clang::PreprocessOnlyAction::ExecuteAction(); + } + +private: + TranslationStores &m_stores; +}; + +class LupdatePreprocessorActionFactory : public clang::tooling::FrontendActionFactory +{ +public: + LupdatePreprocessorActionFactory(TranslationStores &stores) + : m_stores(stores) + {} + +#if (LUPDATE_CLANG_VERSION >= LUPDATE_CLANG_VERSION_CHECK(10,0,0)) + std::unique_ptr<clang::FrontendAction> create() override + { + return std::make_unique<LupdatePreprocessorAction>(m_stores); + } +#else + clang::FrontendAction *create() override + { + return new LupdatePreprocessorAction(m_stores); + } +#endif + +private: + TranslationStores &m_stores; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/linguist/lupdate/main.cpp b/src/linguist/lupdate/main.cpp index 6c4e3db9c..3a3ff76a8 100644 --- a/src/linguist/lupdate/main.cpp +++ b/src/linguist/lupdate/main.cpp @@ -299,7 +299,7 @@ static void printUsage() static bool handleTrFunctionAliases(const QString &arg) { - foreach (const QString &pair, arg.split(QLatin1Char(','), QString::SkipEmptyParts)) { + foreach (const QString &pair, arg.split(QLatin1Char(','), Qt::SkipEmptyParts)) { const int equalSign = pair.indexOf(QLatin1Char('=')); if (equalSign < 0) { printErr(LU::tr("tr-function mapping '%1' in -tr-function-alias is missing the '='.\n").arg(pair)); diff --git a/src/linguist/lupdate/qdeclarative.cpp b/src/linguist/lupdate/qdeclarative.cpp index 7a453aa32..7d995ae86 100644 --- a/src/linguist/lupdate/qdeclarative.cpp +++ b/src/linguist/lupdate/qdeclarative.cpp @@ -39,6 +39,7 @@ #include <private/qqmljslexer_p.h> #include <private/qqmljsastvisitor_p.h> #include <private/qqmljsast_p.h> +#include <private/qqmlapiversion_p.h> #include <QCoreApplication> #include <QFile> @@ -52,6 +53,12 @@ QT_BEGIN_NAMESPACE +#if Q_QML_PRIVATE_API_VERSION < 8 +namespace QQmlJS { + using SourceLocation = AST::SourceLocation; +} +#endif + using namespace QQmlJS; static QString MagicComment(QLatin1String("TRANSLATOR")); @@ -229,7 +236,7 @@ private: void processComments(quint32 offset, bool flush = false); - void processComment(const AST::SourceLocation &loc); + void processComment(const SourceLocation &loc); void consumeComment(); bool createString(AST::ExpressionNode *ast, QString *out) @@ -259,7 +266,7 @@ private: TranslatorMessage::ExtraData extra; QString sourcetext; QString trcontext; - QList<AST::SourceLocation> m_todo; + QList<SourceLocation> m_todo; }; QString createErrorString(const QString &filename, const QString &code, Parser &parser) @@ -274,7 +281,7 @@ QString createErrorString(const QString &filename, const QString &code, Parser & if (m.isWarning()) continue; -#if Q_QML_PRIVATE_API_VERSION < 5 +#if Q_QML_PRIVATE_API_VERSION >= 8 const int line = m.loc.startLine; const int column = m.loc.startColumn; #else @@ -315,7 +322,7 @@ void FindTrCalls::postVisit(AST::Node *node) void FindTrCalls::processComments(quint32 offset, bool flush) { for (; !m_todo.isEmpty(); m_todo.removeFirst()) { - AST::SourceLocation loc = m_todo.first(); + SourceLocation loc = m_todo.first(); if (! flush && (loc.begin() >= offset)) break; @@ -332,7 +339,7 @@ void FindTrCalls::consumeComment() sourcetext.clear(); } -void FindTrCalls::processComment(const AST::SourceLocation &loc) +void FindTrCalls::processComment(const SourceLocation &loc) { if (!loc.length) return; diff --git a/src/linguist/lupdate/ui.cpp b/src/linguist/lupdate/ui.cpp index ce4ecc045..f91ffac5b 100644 --- a/src/linguist/lupdate/ui.cpp +++ b/src/linguist/lupdate/ui.cpp @@ -29,40 +29,41 @@ #include "lupdate.h" #include <translator.h> +#include <xmlparser.h> #include <QtCore/QCoreApplication> #include <QtCore/QDebug> #include <QtCore/QFile> #include <QtCore/QString> - -#include <QtXml/QXmlAttributes> -#include <QtXml/QXmlDefaultHandler> -#include <QtXml/QXmlLocator> -#include <QtXml/QXmlParseException> - +#include <QtCore/QXmlStreamReader> QT_BEGIN_NAMESPACE -class UiReader : public QXmlDefaultHandler +class UiReader : public XmlParser { public: - UiReader(Translator &translator, ConversionData &cd) - : m_translator(translator), m_cd(cd), m_lineNumber(-1), m_isTrString(false), - m_insideStringList(false), m_idBasedTranslations(false) - {} - - bool startElement(const QString &namespaceURI, const QString &localName, - const QString &qName, const QXmlAttributes &atts); - bool endElement(const QString &namespaceURI, const QString &localName, - const QString &qName); - bool characters(const QString &ch); - bool fatalError(const QXmlParseException &exception); - - void setDocumentLocator(QXmlLocator *locator) { m_locator = locator; } + UiReader(Translator &translator, ConversionData &cd, QXmlStreamReader &reader) + : XmlParser(reader), + m_translator(translator), + m_cd(cd), + m_lineNumber(-1), + m_isTrString(false), + m_insideStringList(false), + m_idBasedTranslations(false) + { + } + ~UiReader() override = default; private: + bool startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) override; + bool endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) override; + bool characters(const QStringRef &ch) override; + bool fatalError(qint64 line, qint64 column, const QString &message) override; + void flush(); - void readTranslationAttributes(const QXmlAttributes &atts); + void readTranslationAttributes(const QXmlStreamAttributes &atts); Translator &m_translator; ConversionData &m_cd; @@ -71,7 +72,6 @@ private: QString m_comment; QString m_extracomment; QString m_id; - QXmlLocator *m_locator; QString m_accum; int m_lineNumber; @@ -80,8 +80,8 @@ private: bool m_idBasedTranslations; }; -bool UiReader::startElement(const QString &namespaceURI, - const QString &localName, const QString &qName, const QXmlAttributes &atts) +bool UiReader::startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) { Q_UNUSED(namespaceURI); Q_UNUSED(localName); @@ -95,16 +95,16 @@ bool UiReader::startElement(const QString &namespaceURI, m_insideStringList = true; readTranslationAttributes(atts); } else if (qName == QLatin1String("ui")) { // UI "header" - const int translationTypeIndex = atts.index(QStringLiteral("idbasedtr")); - m_idBasedTranslations = translationTypeIndex >= 0 - && atts.value(translationTypeIndex) == QLatin1String("true"); + const auto attr = QStringLiteral("idbasedtr"); + m_idBasedTranslations = + atts.hasAttribute(attr) && atts.value(attr) == QLatin1String("true"); } m_accum.clear(); return true; } -bool UiReader::endElement(const QString &namespaceURI, - const QString &localName, const QString &qName) +bool UiReader::endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) { Q_UNUSED(namespaceURI); Q_UNUSED(localName); @@ -127,17 +127,18 @@ bool UiReader::endElement(const QString &namespaceURI, return true; } -bool UiReader::characters(const QString &ch) +bool UiReader::characters(const QStringRef &ch) { - m_accum += ch; + m_accum += ch.toString(); return true; } -bool UiReader::fatalError(const QXmlParseException &exception) +bool UiReader::fatalError(qint64 line, qint64 column, const QString &message) { QString msg = LU::tr("XML error: Parse error at line %1, column %2 (%3).") - .arg(exception.lineNumber()).arg(exception.columnNumber()) - .arg(exception.message()); + .arg(line) + .arg(column) + .arg(message); m_cd.appendError(msg); return false; } @@ -160,17 +161,17 @@ void UiReader::flush() } } -void UiReader::readTranslationAttributes(const QXmlAttributes &atts) +void UiReader::readTranslationAttributes(const QXmlStreamAttributes &atts) { - const QString notr = atts.value(QStringLiteral("notr")); + const auto notr = atts.value(QStringLiteral("notr")); if (notr.isEmpty() || notr != QStringLiteral("true")) { m_isTrString = true; - m_comment = atts.value(QStringLiteral("comment")); - m_extracomment = atts.value(QStringLiteral("extracomment")); + m_comment = atts.value(QStringLiteral("comment")).toString(); + m_extracomment = atts.value(QStringLiteral("extracomment")).toString(); if (m_idBasedTranslations) - m_id = atts.value(QStringLiteral("id")); + m_id = atts.value(QStringLiteral("id")).toString(); if (!m_cd.m_noUiLines) - m_lineNumber = m_locator->lineNumber(); + m_lineNumber = static_cast<int>(reader.lineNumber()); } else { m_isTrString = false; } @@ -184,20 +185,14 @@ bool loadUI(Translator &translator, const QString &filename, ConversionData &cd) cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString())); return false; } - QXmlInputSource in(&file); - QXmlSimpleReader reader; - reader.setFeature(QLatin1String("http://xml.org/sax/features/namespaces"), false); - reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true); - reader.setFeature(QLatin1String( - "http://trolltech.com/xml/features/report-whitespace-only-CharData"), false); - UiReader handler(translator, cd); - reader.setContentHandler(&handler); - reader.setErrorHandler(&handler); - bool result = reader.parse(in); + + QXmlStreamReader reader(&file); + reader.setNamespaceProcessing(false); + + UiReader uiReader(translator, cd, reader); + bool result = uiReader.parse(); if (!result) cd.appendError(LU::tr("Parse error in UI file")); - reader.setContentHandler(0); - reader.setErrorHandler(0); return result; } diff --git a/src/linguist/shared/formats.pri b/src/linguist/shared/formats.pri index e5f388f2a..876fe50cc 100644 --- a/src/linguist/shared/formats.pri +++ b/src/linguist/shared/formats.pri @@ -1,17 +1,15 @@ - -# infrastructure -QT *= xml - INCLUDEPATH *= $$PWD SOURCES += \ $$PWD/numerus.cpp \ $$PWD/translator.cpp \ - $$PWD/translatormessage.cpp + $$PWD/translatormessage.cpp \ + $$PWD/xmlparser.cpp HEADERS += \ $$PWD/translator.h \ - $$PWD/translatormessage.h + $$PWD/translatormessage.h \ + $$PWD/xmlparser.h # "real" formats readers and writers SOURCES += \ diff --git a/src/linguist/shared/po.cpp b/src/linguist/shared/po.cpp index 19ba12b96..69062b772 100644 --- a/src/linguist/shared/po.cpp +++ b/src/linguist/shared/po.cpp @@ -548,7 +548,7 @@ bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) QString xrefs; foreach (const QString &ref, codec->toUnicode(item.references).split( - QRegExp(QLatin1String("\\s")), QString::SkipEmptyParts)) { + QRegExp(QLatin1String("\\s")), Qt::SkipEmptyParts)) { int pos = ref.indexOf(QLatin1Char(':')); int lpos = ref.lastIndexOf(QLatin1Char(':')); if (pos != -1 && pos == lpos) { @@ -607,7 +607,7 @@ bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) case ',': { QStringList flags = QString::fromLatin1(line.mid(2)).split( - QRegExp(QLatin1String("[, ]")), QString::SkipEmptyParts); + QRegExp(QLatin1String("[, ]")), Qt::SkipEmptyParts); if (flags.removeOne(QLatin1String("fuzzy"))) item.isFuzzy = true; flags.removeOne(QLatin1String("qt-format")); @@ -750,7 +750,7 @@ bool savePO(const Translator &translator, QIODevice &dev, ConversionData &) out << "msgid \"\"\n"; Translator::ExtraData headers = translator.extras(); QStringList hdrOrder = translator.extra(QLatin1String("po-headers")).split( - QLatin1Char(','), QString::SkipEmptyParts); + QLatin1Char(','), Qt::SkipEmptyParts); // Keep in sync with loadPO addPoHeader(headers, hdrOrder, "MIME-Version", QLatin1String("1.0")); addPoHeader(headers, hdrOrder, "Content-Type", diff --git a/src/linguist/shared/proitems.h b/src/linguist/shared/proitems.h index c7af53b1c..37a25196a 100644 --- a/src/linguist/shared/proitems.h +++ b/src/linguist/shared/proitems.h @@ -67,6 +67,7 @@ class ProString { public: ProString(); ProString(const ProString &other); + ProString &operator=(const ProString &) = default; PROITEM_EXPLICIT ProString(const QString &str); PROITEM_EXPLICIT ProString(const QStringRef &str); PROITEM_EXPLICIT ProString(const char *str); diff --git a/src/linguist/shared/qm.cpp b/src/linguist/shared/qm.cpp index 6963ad6cb..288607824 100644 --- a/src/linguist/shared/qm.cpp +++ b/src/linguist/shared/qm.cpp @@ -151,9 +151,9 @@ public: uint o; }; - enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96 }; + enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96, Language = 0xa7 }; - Releaser() {} + Releaser(const QString &language) : m_language(language) {} bool save(QIODevice *iod); @@ -179,6 +179,7 @@ private: void writeMessage(const ByteTranslatorMessage & msg, QDataStream & stream, TranslatorSaveMode strip, Prefix prefix) const; + QString m_language; // for squeezed but non-file data, this is what needs to be deleted QByteArray m_messageArray; QByteArray m_offsetArray; @@ -249,6 +250,12 @@ bool Releaser::save(QIODevice *iod) QDataStream s(iod); s.writeRawData((const char *)magic, MagicLength); + if (!m_language.isEmpty()) { + QByteArray lang = originalBytes(m_language); + quint32 las = quint32(lang.size()); + s << quint8(Language) << las; + s.writeRawData(lang, las); + } if (!m_dependencyArray.isEmpty()) { quint32 das = quint32(m_dependencyArray.size()); s << quint8(Dependencies) << das; @@ -465,7 +472,7 @@ bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) return false; } - enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96 }; + enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88, Dependencies = 0x96, Language = 0xa7 }; // for squeezed but non-file data, this is what needs to be deleted const uchar *messageArray = 0; @@ -473,6 +480,7 @@ bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) uint offsetLength = 0; bool ok = true; + bool utf8Fail = false; const uchar *end = data + len; data += MagicLength; @@ -505,6 +513,10 @@ bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) dependencies.append(dep); } translator.setDependencies(dependencies); + } else if (tag == Language) { + QString language; + fromBytes((const char *)data, blockLen, &language, &utf8Fail); + translator.setLanguageCode(language); } data += blockLen; @@ -524,7 +536,6 @@ bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) guessPlurals = (numerusForms.count() == 1); QString context, sourcetext, comment; - bool utf8Fail = false; QStringList translations; for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) { @@ -632,7 +643,7 @@ static bool containsStripped(const Translator &translator, const TranslatorMessa bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd) { - Releaser releaser; + Releaser releaser(translator.languageCode()); QLocale::Language l; QLocale::Country c; Translator::languageAndCountry(translator.languageCode(), &l, &c); diff --git a/src/linguist/shared/qmakebuiltins.cpp b/src/linguist/shared/qmakebuiltins.cpp index 92366b9f7..180c1fa7a 100644 --- a/src/linguist/shared/qmakebuiltins.cpp +++ b/src/linguist/shared/qmakebuiltins.cpp @@ -768,7 +768,7 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinExpand( const auto vars = values(map(args.at(0))); for (const ProString &var : vars) { // FIXME: this is inconsistent with the "there are no empty strings" dogma. - const auto splits = var.toQStringRef().split(sep, QString::KeepEmptyParts); + const auto splits = var.toQStringRef().split(sep, Qt::KeepEmptyParts); for (const auto &splt : splits) ret << ProString(splt).setSource(var); } @@ -1538,7 +1538,7 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional( if (args.count() == 1) return returnBool(isActiveConfig(args.at(0).toQStringRef())); const auto &mutuals = args.at(1).toQStringRef().split(QLatin1Char('|'), - QString::SkipEmptyParts); + Qt::SkipEmptyParts); const ProStringList &configs = values(statics.strCONFIG); for (int i = configs.size() - 1; i >= 0; i--) { @@ -1572,7 +1572,7 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional( } } else { const auto mutuals = args.at(2).toQStringRef().split(QLatin1Char('|'), - QString::SkipEmptyParts); + Qt::SkipEmptyParts); for (int i = l.size() - 1; i >= 0; i--) { const ProString &val = l[i]; for (int mut = 0; mut < mutuals.count(); mut++) { diff --git a/src/linguist/shared/qmakeevaluator.cpp b/src/linguist/shared/qmakeevaluator.cpp index 639114d20..9e3fbf3e0 100644 --- a/src/linguist/shared/qmakeevaluator.cpp +++ b/src/linguist/shared/qmakeevaluator.cpp @@ -883,7 +883,7 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::visitProVariable( return ReturnTrue; } QChar sep = val.at(1); - auto func = val.split(sep, QString::KeepEmptyParts); + auto func = val.split(sep, Qt::KeepEmptyParts); if (func.count() < 3 || func.count() > 4) { evalError(fL1S("The s/// function expects 3 or 4 arguments.")); return ReturnTrue; @@ -1022,7 +1022,7 @@ static ProString msvcArchitecture(const QString &vcInstallDir, const QString &pa QString vcBinDir = vcInstallDir; if (vcBinDir.endsWith(QLatin1Char('\\'))) vcBinDir.chop(1); - const auto dirs = pathVar.split(QLatin1Char(';'), QString::SkipEmptyParts); + const auto dirs = pathVar.split(QLatin1Char(';'), Qt::SkipEmptyParts); for (const QString &dir : dirs) { if (!dir.startsWith(vcBinDir, Qt::CaseInsensitive)) continue; diff --git a/src/linguist/shared/qmakeglobals.cpp b/src/linguist/shared/qmakeglobals.cpp index 6ba7abf28..0a58e9e29 100644 --- a/src/linguist/shared/qmakeglobals.cpp +++ b/src/linguist/shared/qmakeglobals.cpp @@ -261,7 +261,7 @@ QStringList QMakeGlobals::splitPathList(const QString &val) const QStringList ret; if (!val.isEmpty()) { QString cwd(QDir::currentPath()); - const QStringList vals = val.split(dirlist_sep, QString::SkipEmptyParts); + const QStringList vals = val.split(dirlist_sep, Qt::SkipEmptyParts); ret.reserve(vals.length()); for (const QString &it : vals) ret << IoUtils::resolvePath(cwd, it); diff --git a/src/linguist/shared/qmakeparser.h b/src/linguist/shared/qmakeparser.h index ae76d8c46..22da3c69f 100644 --- a/src/linguist/shared/qmakeparser.h +++ b/src/linguist/shared/qmakeparser.h @@ -111,7 +111,6 @@ private: struct BlockScope { BlockScope() : start(nullptr), braceLevel(0), special(false), inBranch(false), nest(NestNone) {} - BlockScope(const BlockScope &other) { *this = other; } ushort *start; // Where this block started; store length here int braceLevel; // Nesting of braces in scope bool special; // Single-line conditionals inside loops, etc. cannot have else branches diff --git a/src/linguist/shared/xliff.cpp b/src/linguist/shared/xliff.cpp index c499e9ea5..bc47289b4 100644 --- a/src/linguist/shared/xliff.cpp +++ b/src/linguist/shared/xliff.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "translator.h" +#include "xmlparser.h" #include <QtCore/QDebug> #include <QtCore/QMap> @@ -36,11 +37,6 @@ #include <QtCore/QTextCodec> #include <QtCore/QTextStream> -#include <QtXml/QXmlAttributes> -#include <QtXml/QXmlDefaultHandler> -#include <QtXml/QXmlParseException> - - // The string value is historical and reflects the main purpose: Keeping // obsolete entries separate from the magic file message (which both have // no location information, but typically reside at opposite ends of the file). @@ -368,22 +364,22 @@ static void writeMessage(QTextStream &ts, const TranslatorMessage &msg, const QR } } - -class XLIFFHandler : public QXmlDefaultHandler +class XLIFFHandler : public XmlParser { public: - XLIFFHandler(Translator &translator, ConversionData &cd); + XLIFFHandler(Translator &translator, ConversionData &cd, QXmlStreamReader &reader); + ~XLIFFHandler() override = default; - bool startElement(const QString& namespaceURI, const QString &localName, - const QString &qName, const QXmlAttributes &atts ); - bool endElement(const QString& namespaceURI, const QString &localName, - const QString &qName ); - bool characters(const QString &ch); - bool fatalError(const QXmlParseException &exception); +private: + bool startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) override; + bool endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) override; + bool characters(const QStringRef &ch) override; + bool fatalError(qint64 line, qint64 column, const QString &message) override; - bool endDocument(); + bool endDocument() override; -private: enum XliffContext { XC_xliff, XC_group, @@ -442,14 +438,16 @@ private: QStack<int> m_contextStack; }; -XLIFFHandler::XLIFFHandler(Translator &translator, ConversionData &cd) - : m_translator(translator), m_cd(cd), - m_translate(true), - m_approved(true), - m_lineNumber(-1), - m_URITT(QLatin1String(TrollTsNamespaceURI)), - m_URI(QLatin1String(XLIFF11namespaceURI)), - m_URI12(QLatin1String(XLIFF12namespaceURI)) +XLIFFHandler::XLIFFHandler(Translator &translator, ConversionData &cd, QXmlStreamReader &reader) + : XmlParser(reader, true), + m_translator(translator), + m_cd(cd), + m_translate(true), + m_approved(true), + m_lineNumber(-1), + m_URITT(QLatin1String(TrollTsNamespaceURI)), + m_URI(QLatin1String(XLIFF11namespaceURI)), + m_URI12(QLatin1String(XLIFF12namespaceURI)) {} @@ -485,33 +483,35 @@ bool XLIFFHandler::hasContext(XliffContext ctx) const return false; } -bool XLIFFHandler::startElement(const QString& namespaceURI, - const QString &localName, const QString &qName, const QXmlAttributes &atts ) +bool XLIFFHandler::startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) { Q_UNUSED(qName); if (namespaceURI == m_URITT) goto bail; - if (namespaceURI != m_URI && namespaceURI != m_URI12) - return false; + if (namespaceURI != m_URI && namespaceURI != m_URI12) { + return fatalError(reader.lineNumber(), reader.columnNumber(), + QLatin1String("Unknown namespace in the XLIFF file")); + } if (localName == QLatin1String("xliff")) { // make sure that the stack is not empty during parsing pushContext(XC_xliff); } else if (localName == QLatin1String("file")) { - m_fileName = atts.value(QLatin1String("original")); - m_language = atts.value(QLatin1String("target-language")); + m_fileName = atts.value(QLatin1String("original")).toString(); + m_language = atts.value(QLatin1String("target-language")).toString(); m_language.replace(QLatin1Char('-'), QLatin1Char('_')); - m_sourceLanguage = atts.value(QLatin1String("source-language")); + m_sourceLanguage = atts.value(QLatin1String("source-language")).toString(); m_sourceLanguage.replace(QLatin1Char('-'), QLatin1Char('_')); if (m_sourceLanguage == QLatin1String("en")) m_sourceLanguage.clear(); } else if (localName == QLatin1String("group")) { if (atts.value(QLatin1String("restype")) == QLatin1String(restypeContext)) { - m_context = atts.value(QLatin1String("resname")); + m_context = atts.value(QLatin1String("resname")).toString(); pushContext(XC_restype_context); } else { if (atts.value(QLatin1String("restype")) == QLatin1String(restypePlurals)) { pushContext(XC_restype_plurals); - m_id = atts.value(QLatin1String("id")); + m_id = atts.value(QLatin1String("id")).toString(); if (atts.value(QLatin1String("translate")) == QLatin1String("no")) m_translate = false; } else { @@ -523,7 +523,7 @@ bool XLIFFHandler::startElement(const QString& namespaceURI, if (atts.value(QLatin1String("translate")) == QLatin1String("no")) m_translate = false; if (!hasContext(XC_restype_plurals)) { - m_id = atts.value(QLatin1String("id")); + m_id = atts.value(QLatin1String("id")).toString(); if (m_id.startsWith(QLatin1String("_msg"))) m_id.clear(); } @@ -539,19 +539,18 @@ bool XLIFFHandler::startElement(const QString& namespaceURI, if (atts.value(QLatin1String("restype")) != QLatin1String(restypeDummy)) pushContext(XC_restype_translation); } else if (localName == QLatin1String("context-group")) { - QString purpose = atts.value(QLatin1String("purpose")); - if (purpose == QLatin1String("location")) + if (atts.value(QLatin1String("purpose")) == QLatin1String("location")) pushContext(XC_context_group); else pushContext(XC_context_group_any); } else if (currentContext() == XC_context_group && localName == QLatin1String("context")) { - QString ctxtype = atts.value(QLatin1String("context-type")); + const auto ctxtype = atts.value(QLatin1String("context-type")); if (ctxtype == QLatin1String("linenumber")) pushContext(XC_context_linenumber); else if (ctxtype == QLatin1String("sourcefile")) pushContext(XC_context_filename); } else if (currentContext() == XC_context_group_any && localName == QLatin1String("context")) { - QString ctxtype = atts.value(QLatin1String("context-type")); + const auto ctxtype = atts.value(QLatin1String("context-type")); if (ctxtype == QLatin1String(contextMsgctxt)) pushContext(XC_context_comment); else if (ctxtype == QLatin1String(contextOldMsgctxt)) @@ -563,7 +562,7 @@ bool XLIFFHandler::startElement(const QString& namespaceURI, else pushContext(XC_translator_comment); } else if (localName == QLatin1String("ph")) { - QString ctype = atts.value(QLatin1String("ctype")); + QString ctype = atts.value(QLatin1String("ctype")).toString(); if (ctype.startsWith(QLatin1String("x-ch-"))) m_ctype = ctype.mid(5); pushContext(XC_ph); @@ -574,19 +573,21 @@ bail: return true; } -bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localName, - const QString &qName) +bool XLIFFHandler::endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) { Q_UNUSED(qName); if (namespaceURI == m_URITT) { if (hasContext(XC_trans_unit) || hasContext(XC_restype_plurals)) - m_extra[localName] = accum; + m_extra[localName.toString()] = accum; else - m_translator.setExtra(localName, accum); + m_translator.setExtra(localName.toString(), accum); return true; } - if (namespaceURI != m_URI && namespaceURI != m_URI12) - return false; + if (namespaceURI != m_URI && namespaceURI != m_URI12) { + return fatalError(reader.lineNumber(), reader.columnNumber(), + QLatin1String("Unknown namespace in the XLIFF file")); + } //qDebug() << "URI:" << namespaceURI << "QNAME:" << qName; if (localName == QLatin1String("xliff")) { popContext(XC_xliff); @@ -640,15 +641,19 @@ bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localN if (!m_hadAlt) m_oldSources.append(QString()); if (!hasContext(XC_restype_plurals)) { - if (!finalizeMessage(false)) - return false; + if (!finalizeMessage(false)) { + return fatalError(reader.lineNumber(), reader.columnNumber(), + QLatin1String("Element processing failed")); + } } } else if (localName == QLatin1String("alt-trans")) { popContext(XC_alt_trans); } else if (localName == QLatin1String("group")) { if (popContext(XC_restype_plurals)) { - if (!finalizeMessage(true)) - return false; + if (!finalizeMessage(true)) { + return fatalError(reader.lineNumber(), reader.columnNumber(), + QLatin1String("Element processing failed")); + } } else if (popContext(XC_restype_context)) { m_context.clear(); } else { @@ -658,7 +663,7 @@ bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localN return true; } -bool XLIFFHandler::characters(const QString &ch) +bool XLIFFHandler::characters(const QStringRef &ch) { if (currentContext() == XC_ph) { // handle the content of <ph> elements @@ -670,7 +675,7 @@ bool XLIFFHandler::characters(const QString &ch) accum.append(chr); } } else { - QString t = ch; + QString t = ch.toString(); t.replace(QLatin1String("\r"), QLatin1String("")); accum.append(t); } @@ -730,23 +735,20 @@ bool XLIFFHandler::finalizeMessage(bool isPlural) return true; } -bool XLIFFHandler::fatalError(const QXmlParseException &exception) +bool XLIFFHandler::fatalError(qint64 line, qint64 column, const QString &message) { QString msg = QString::asprintf("XML error: Parse error at line %d, column %d (%s).\n", - exception.lineNumber(), exception.columnNumber(), - exception.message().toLatin1().data()); + static_cast<int>(line), static_cast<int>(column), + message.toLatin1().data()); m_cd.appendError(msg); return false; } bool loadXLIFF(Translator &translator, QIODevice &dev, ConversionData &cd) { - QXmlInputSource in(&dev); - QXmlSimpleReader reader; - XLIFFHandler hand(translator, cd); - reader.setContentHandler(&hand); - reader.setErrorHandler(&hand); - return reader.parse(in); + QXmlStreamReader reader(&dev); + XLIFFHandler hand(translator, cd, reader); + return hand.parse(); } bool saveXLIFF(const Translator &translator, QIODevice &dev, ConversionData &cd) diff --git a/src/linguist/shared/xmlparser.cpp b/src/linguist/shared/xmlparser.cpp new file mode 100644 index 000000000..bcdca12bb --- /dev/null +++ b/src/linguist/shared/xmlparser.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "xmlparser.h" + +QT_BEGIN_NAMESPACE + +bool XmlParser::parse() +{ + while (!reader.atEnd()) { + reader.readNext(); + if (reader.hasError()) { + fatalError(reader.lineNumber(), reader.columnNumber(), reader.errorString()); + return false; + } + + switch (reader.tokenType()) { + case QXmlStreamReader::StartElement: + if (!startElement(reader.namespaceUri(), reader.name(), reader.qualifiedName(), + reader.attributes())) { + return false; + } + break; + case QXmlStreamReader::EndElement: + if (!endElement(reader.namespaceUri(), reader.name(), reader.qualifiedName())) { + return false; + } + break; + case QXmlStreamReader::Characters: + if (reportWhitespaceOnlyData + || (!reader.isWhitespace() && !reader.text().toString().trimmed().isEmpty())) { + if (!characters(reader.text())) + return false; + } + break; + default: + break; + } + } + if (reader.isEndDocument() && !endDocument()) + return false; + + return true; +} + +bool XmlParser::startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts) +{ + Q_UNUSED(namespaceURI) + Q_UNUSED(localName) + Q_UNUSED(qName) + Q_UNUSED(atts) + return true; +} + +bool XmlParser::endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName) +{ + Q_UNUSED(namespaceURI) + Q_UNUSED(localName) + Q_UNUSED(qName) + return true; +} + +bool XmlParser::characters(const QStringRef &text) +{ + Q_UNUSED(text) + return true; +} + +bool XmlParser::fatalError(qint64 line, qint64 column, const QString &message) +{ + Q_UNUSED(line) + Q_UNUSED(column) + Q_UNUSED(message) + return true; +} + +bool XmlParser::endDocument() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/linguist/shared/xmlparser.h b/src/linguist/shared/xmlparser.h new file mode 100644 index 000000000..4028c7067 --- /dev/null +++ b/src/linguist/shared/xmlparser.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef XMLPARSER_H +#define XMLPARSER_H + +#include <QtCore/qglobal.h> +#include <QtCore/qxmlstream.h> + +QT_BEGIN_NAMESPACE + +class XmlParser +{ +public: + XmlParser(QXmlStreamReader &r, bool whitespaceOnlyData = false) + : reader(r), reportWhitespaceOnlyData(whitespaceOnlyData) + { + } + virtual ~XmlParser() = default; + + bool parse(); + +protected: + virtual bool startElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName, const QXmlStreamAttributes &atts); + virtual bool endElement(const QStringRef &namespaceURI, const QStringRef &localName, + const QStringRef &qName); + virtual bool characters(const QStringRef &text); + virtual bool endDocument(); + virtual bool fatalError(qint64 line, qint64 column, const QString &message); + + QXmlStreamReader &reader; + bool reportWhitespaceOnlyData; +}; + +QT_END_NAMESPACE + +#endif // XMLPARSER_H |