summaryrefslogtreecommitdiffstats
path: root/src/linguist/lupdate/qdeclarative.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/linguist/lupdate/qdeclarative.cpp')
-rw-r--r--src/linguist/lupdate/qdeclarative.cpp433
1 files changed, 433 insertions, 0 deletions
diff --git a/src/linguist/lupdate/qdeclarative.cpp b/src/linguist/lupdate/qdeclarative.cpp
new file mode 100644
index 000000000..6bf9cf469
--- /dev/null
+++ b/src/linguist/lupdate/qdeclarative.cpp
@@ -0,0 +1,433 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Linguist of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "lupdate.h"
+
+#include <translator.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QtCore/QString>
+
+#include "parser/qdeclarativejsengine_p.h"
+#include "parser/qdeclarativejsparser_p.h"
+#include "parser/qdeclarativejslexer_p.h"
+#include "parser/qdeclarativejsnodepool_p.h"
+#include "parser/qdeclarativejsastvisitor_p.h"
+#include "parser/qdeclarativejsast_p.h"
+
+#include <QCoreApplication>
+#include <QFile>
+#include <QFileInfo>
+#include <QtDebug>
+#include <QStringList>
+
+#include <iostream>
+#include <cstdlib>
+#include <cctype>
+
+QT_BEGIN_NAMESPACE
+
+class LU {
+ Q_DECLARE_TR_FUNCTIONS(LUpdate)
+};
+
+using namespace QDeclarativeJS;
+
+class Comment
+{
+public:
+ Comment() : lastLine(-1) {}
+ QString extracomment;
+ QString msgid;
+ TranslatorMessage::ExtraData extra;
+ QString sourcetext;
+ int lastLine;
+
+ bool isValid() const
+ { return !extracomment.isEmpty() || !msgid.isEmpty() || !sourcetext.isEmpty() || !extra.isEmpty(); }
+};
+
+class FindTrCalls: protected AST::Visitor
+{
+public:
+ void operator()(Translator *translator, const QString &fileName, AST::Node *node)
+ {
+ m_translator = translator;
+ m_fileName = fileName;
+ m_component = QFileInfo(fileName).baseName(); //matches qsTr usage in QScriptEngine
+ accept(node);
+ }
+
+ QList<Comment> comments;
+
+protected:
+ using AST::Visitor::visit;
+ using AST::Visitor::endVisit;
+
+ void accept(AST::Node *node)
+ { AST::Node::acceptChild(node, this); }
+
+ virtual void endVisit(AST::CallExpression *node)
+ {
+ m_bSource.clear();
+ if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(node->base)) {
+ if (idExpr->name->asString() == QLatin1String("qsTr") ||
+ idExpr->name->asString() == QLatin1String("QT_TR_NOOP")) {
+ if (!node->arguments)
+ return;
+ AST::BinaryExpression *binary = AST::cast<AST::BinaryExpression *>(node->arguments->expression);
+ if (binary) {
+ if (!createString(binary))
+ m_bSource.clear();
+ }
+ AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
+ if (literal || !m_bSource.isEmpty()) {
+ const QString source = literal ? literal->value->asString() : m_bSource;
+
+ QString comment;
+ bool plural = false;
+ AST::ArgumentList *commentNode = node->arguments->next;
+ if (commentNode && AST::cast<AST::StringLiteral *>(commentNode->expression)) {
+ literal = AST::cast<AST::StringLiteral *>(commentNode->expression);
+ comment = literal->value->asString();
+
+ AST::ArgumentList *nNode = commentNode->next;
+ if (nNode)
+ plural = true;
+ }
+
+ QString id;
+ QString extracomment;
+ TranslatorMessage::ExtraData extra;
+ Comment scomment = findComment(node->firstSourceLocation().startLine);
+ if (scomment.isValid()) {
+ extracomment = scomment.extracomment;
+ extra = scomment.extra;
+ id = scomment.msgid;
+ }
+
+ TranslatorMessage msg(m_component, source,
+ comment, QString(), m_fileName,
+ node->firstSourceLocation().startLine, QStringList(),
+ TranslatorMessage::Unfinished, plural);
+ msg.setExtraComment(extracomment.simplified());
+ msg.setId(id);
+ msg.setExtras(extra);
+ m_translator->extend(msg);
+ }
+ } else if (idExpr->name->asString() == QLatin1String("qsTranslate") ||
+ idExpr->name->asString() == QLatin1String("QT_TRANSLATE_NOOP")) {
+ if (node->arguments && AST::cast<AST::StringLiteral *>(node->arguments->expression)) {
+ AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
+ const QString context = literal->value->asString();
+
+ QString source;
+ QString comment;
+ bool plural = false;
+ AST::ArgumentList *sourceNode = node->arguments->next;
+ if (!sourceNode)
+ return;
+ literal = AST::cast<AST::StringLiteral *>(sourceNode->expression);
+ AST::BinaryExpression *binary = AST::cast<AST::BinaryExpression *>(sourceNode->expression);
+ if (binary) {
+ if (!createString(binary))
+ m_bSource.clear();
+ }
+ if (!literal && m_bSource.isEmpty())
+ return;
+
+ QString id;
+ QString extracomment;
+ TranslatorMessage::ExtraData extra;
+ Comment scomment = findComment(node->firstSourceLocation().startLine);
+ if (scomment.isValid()) {
+ extracomment = scomment.extracomment;
+ extra = scomment.extra;
+ id = scomment.msgid;
+ }
+
+ source = literal ? literal->value->asString() : m_bSource;
+ AST::ArgumentList *commentNode = sourceNode->next;
+ if (commentNode && AST::cast<AST::StringLiteral *>(commentNode->expression)) {
+ literal = AST::cast<AST::StringLiteral *>(commentNode->expression);
+ comment = literal->value->asString();
+
+ AST::ArgumentList *nNode = commentNode->next;
+ if (nNode)
+ plural = true;
+ }
+
+ TranslatorMessage msg(context, source,
+ comment, QString(), m_fileName,
+ node->firstSourceLocation().startLine, QStringList(),
+ TranslatorMessage::Unfinished, plural);
+ msg.setExtraComment(extracomment.simplified());
+ msg.setId(id);
+ msg.setExtras(extra);
+ m_translator->extend(msg);
+ }
+ } else if (idExpr->name->asString() == QLatin1String("qsTrId") ||
+ idExpr->name->asString() == QLatin1String("QT_TRID_NOOP")) {
+ if (!node->arguments)
+ return;
+
+ AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
+ if (literal) {
+
+ QString extracomment;
+ QString sourcetext;
+ TranslatorMessage::ExtraData extra;
+ Comment comment = findComment(node->firstSourceLocation().startLine);
+ if (comment.isValid()) {
+ extracomment = comment.extracomment;
+ sourcetext = comment.sourcetext;
+ extra = comment.extra;
+ }
+
+ const QString id = literal->value->asString();
+ bool plural = node->arguments->next;
+
+ TranslatorMessage msg(QString(), sourcetext,
+ QString(), QString(), m_fileName,
+ node->firstSourceLocation().startLine, QStringList(),
+ TranslatorMessage::Unfinished, plural);
+ msg.setExtraComment(extracomment.simplified());
+ msg.setId(id);
+ msg.setExtras(extra);
+ m_translator->extend(msg);
+ }
+ }
+ }
+ }
+
+private:
+ bool createString(AST::BinaryExpression *b)
+ {
+ if (!b || b->op != 0)
+ return false;
+ AST::BinaryExpression *l = AST::cast<AST::BinaryExpression *>(b->left);
+ AST::BinaryExpression *r = AST::cast<AST::BinaryExpression *>(b->right);
+ AST::StringLiteral *ls = AST::cast<AST::StringLiteral *>(b->left);
+ AST::StringLiteral *rs = AST::cast<AST::StringLiteral *>(b->right);
+ if ((!l && !ls) || (!r && !rs))
+ return false;
+ if (l) {
+ if (!createString(l))
+ return false;
+ } else
+ m_bSource.prepend(ls->value->asString());
+
+ if (r) {
+ if (!createString(r))
+ return false;
+ } else
+ m_bSource.append(rs->value->asString());
+
+ return true;
+ }
+
+ Comment findComment(int loc)
+ {
+ if (comments.isEmpty())
+ return Comment();
+
+ int i = 0;
+ int commentLoc = comments.at(i).lastLine;
+ while (commentLoc <= loc) {
+ if (commentLoc == loc)
+ return comments.at(i);
+ if (i == comments.count()-1)
+ break;
+ commentLoc = comments.at(++i).lastLine;
+ }
+ return Comment();
+ }
+
+ Translator *m_translator;
+ QString m_fileName;
+ QString m_component;
+ QString m_bSource;
+};
+
+QString createErrorString(const QString &filename, const QString &code, Parser &parser)
+{
+ // print out error
+ QStringList lines = code.split(QLatin1Char('\n'));
+ lines.append(QLatin1String("\n")); // sentinel.
+ QString errorString;
+
+ foreach (const DiagnosticMessage &m, parser.diagnosticMessages()) {
+
+ if (m.isWarning())
+ continue;
+
+ QString error = filename + QLatin1Char(':') + QString::number(m.loc.startLine)
+ + QLatin1Char(':') + QString::number(m.loc.startColumn) + QLatin1String(": error: ")
+ + m.message + QLatin1Char('\n');
+
+ int line = 0;
+ if (m.loc.startLine > 0)
+ line = m.loc.startLine - 1;
+
+ const QString textLine = lines.at(line);
+
+ error += textLine + QLatin1Char('\n');
+
+ int column = m.loc.startColumn - 1;
+ if (column < 0)
+ column = 0;
+
+ column = qMin(column, textLine.length());
+
+ for (int i = 0; i < column; ++i) {
+ const QChar ch = textLine.at(i);
+ if (ch.isSpace())
+ error += ch.unicode();
+ else
+ error += QLatin1Char(' ');
+ }
+ error += QLatin1String("^\n");
+ errorString += error;
+ }
+ return errorString;
+}
+
+bool processComment(const QChar *chars, int length, Comment &comment)
+{
+ // Try to match the logic of the QtScript parser.
+ if (!length)
+ return comment.isValid();
+ if (*chars == QLatin1Char(':') && chars[1].isSpace()) {
+ comment.extracomment += QString(chars+1, length-1);
+ } else if (*chars == QLatin1Char('=') && chars[1].isSpace()) {
+ comment.msgid = QString(chars+2, length-2).simplified();
+ } else if (*chars == QLatin1Char('~') && chars[1].isSpace()) {
+ QString text = QString(chars+2, length-2).trimmed();
+ int k = text.indexOf(QLatin1Char(' '));
+ if (k > -1)
+ comment.extra.insert(text.left(k), text.mid(k + 1).trimmed());
+ } else if (*chars == QLatin1Char('%') && chars[1].isSpace()) {
+ comment.sourcetext.reserve(comment.sourcetext.length() + length-2);
+ ushort *ptr = (ushort *)comment.sourcetext.data() + comment.sourcetext.length();
+ int p = 2, c;
+ forever {
+ if (p >= length)
+ break;
+ c = chars[p++].unicode();
+ if (isspace(c))
+ continue;
+ if (c != '"')
+ break;
+ forever {
+ if (p >= length)
+ break;
+ c = chars[p++].unicode();
+ if (c == '"')
+ break;
+ if (c == '\\') {
+ if (p >= length)
+ break;
+ c = chars[p++].unicode();
+ if (c == '\n')
+ break;
+ *ptr++ = '\\';
+ }
+ *ptr++ = c;
+ }
+ }
+ comment.sourcetext.resize(ptr - (ushort *)comment.sourcetext.data());
+ }
+ return comment.isValid();
+}
+
+bool loadQml(Translator &translator, const QString &filename, ConversionData &cd)
+{
+ cd.m_sourceFileName = filename;
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadOnly)) {
+ cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString()));
+ return false;
+ }
+
+ const QString code = QTextStream(&file).readAll();
+
+ Engine driver;
+ Parser parser(&driver);
+
+ NodePool nodePool(filename, &driver);
+ driver.setNodePool(&nodePool);
+
+ Lexer lexer(&driver);
+ lexer.setCode(code, /*line = */ 1);
+ driver.setLexer(&lexer);
+
+ if (parser.parse()) {
+ FindTrCalls trCalls;
+
+ // build up a list of comments that contain translation information.
+ for (int i = 0; i < driver.comments().size(); ++i) {
+ AST::SourceLocation loc = driver.comments().at(i);
+ QString commentStr = code.mid(loc.offset, loc.length);
+
+ if (trCalls.comments.isEmpty() || trCalls.comments.last().lastLine != int(loc.startLine)) {
+ Comment comment;
+ comment.lastLine = loc.startLine+1;
+ if (processComment(commentStr.constData(), commentStr.length(), comment))
+ trCalls.comments.append(comment);
+ } else {
+ Comment &lastComment = trCalls.comments.last();
+ lastComment.lastLine += 1;
+ processComment(commentStr.constData(), commentStr.length(), lastComment);
+ }
+ }
+
+ //find all tr calls in the code
+ trCalls(&translator, filename, parser.ast());
+ } else {
+ QString error = createErrorString(filename, code, parser);
+ cd.appendError(error);
+ return false;
+ }
+ return true;
+}
+
+QT_END_NAMESPACE