[844] | 1 | /****************************************************************************
|
---|
| 2 | **
|
---|
| 3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
---|
| 4 | ** All rights reserved.
|
---|
| 5 | ** Contact: Nokia Corporation (qt-info@nokia.com)
|
---|
| 6 | **
|
---|
| 7 | ** This file is part of the Qt Linguist of the Qt Toolkit.
|
---|
| 8 | **
|
---|
| 9 | ** $QT_BEGIN_LICENSE:LGPL$
|
---|
| 10 | ** Commercial Usage
|
---|
| 11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
| 12 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
| 13 | ** Software or, alternatively, in accordance with the terms contained in
|
---|
| 14 | ** a written agreement between you and Nokia.
|
---|
| 15 | **
|
---|
| 16 | ** GNU Lesser General Public License Usage
|
---|
| 17 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
---|
| 18 | ** General Public License version 2.1 as published by the Free Software
|
---|
| 19 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
---|
| 20 | ** packaging of this file. Please review the following information to
|
---|
| 21 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
---|
| 22 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
---|
| 23 | **
|
---|
| 24 | ** In addition, as a special exception, Nokia gives you certain additional
|
---|
| 25 | ** rights. These rights are described in the Nokia Qt LGPL Exception
|
---|
| 26 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
---|
| 27 | **
|
---|
| 28 | ** GNU General Public License Usage
|
---|
| 29 | ** Alternatively, this file may be used under the terms of the GNU
|
---|
| 30 | ** General Public License version 3.0 as published by the Free Software
|
---|
| 31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
| 32 | ** packaging of this file. Please review the following information to
|
---|
| 33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
---|
| 34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
---|
| 35 | **
|
---|
| 36 | ** If you have questions regarding the use of this file, please contact
|
---|
| 37 | ** Nokia at qt-info@nokia.com.
|
---|
| 38 | ** $QT_END_LICENSE$
|
---|
| 39 | **
|
---|
| 40 | ****************************************************************************/
|
---|
| 41 |
|
---|
| 42 | #include "lupdate.h"
|
---|
| 43 |
|
---|
| 44 | #include <translator.h>
|
---|
| 45 |
|
---|
| 46 | #include <QtCore/QDebug>
|
---|
| 47 | #include <QtCore/QFile>
|
---|
| 48 | #include <QtCore/QString>
|
---|
| 49 |
|
---|
| 50 | #include "parser/qdeclarativejsengine_p.h"
|
---|
| 51 | #include "parser/qdeclarativejsparser_p.h"
|
---|
| 52 | #include "parser/qdeclarativejslexer_p.h"
|
---|
| 53 | #include "parser/qdeclarativejsnodepool_p.h"
|
---|
| 54 | #include "parser/qdeclarativejsastvisitor_p.h"
|
---|
| 55 | #include "parser/qdeclarativejsast_p.h"
|
---|
| 56 |
|
---|
| 57 | #include <QCoreApplication>
|
---|
| 58 | #include <QFile>
|
---|
| 59 | #include <QFileInfo>
|
---|
| 60 | #include <QtDebug>
|
---|
| 61 | #include <QStringList>
|
---|
| 62 |
|
---|
| 63 | #include <iostream>
|
---|
| 64 | #include <cstdlib>
|
---|
| 65 |
|
---|
| 66 | QT_BEGIN_NAMESPACE
|
---|
| 67 |
|
---|
| 68 | class LU {
|
---|
| 69 | Q_DECLARE_TR_FUNCTIONS(LUpdate)
|
---|
| 70 | };
|
---|
| 71 |
|
---|
| 72 | using namespace QDeclarativeJS;
|
---|
| 73 |
|
---|
| 74 | class Comment
|
---|
| 75 | {
|
---|
| 76 | public:
|
---|
| 77 | Comment() : lastLine(-1) {}
|
---|
| 78 | QString extracomment;
|
---|
| 79 | QString msgid;
|
---|
| 80 | TranslatorMessage::ExtraData extra;
|
---|
| 81 | QString sourcetext;
|
---|
| 82 | int lastLine;
|
---|
| 83 |
|
---|
| 84 | bool isValid() const
|
---|
| 85 | { return !extracomment.isEmpty() || !msgid.isEmpty() || !sourcetext.isEmpty() || !extra.isEmpty(); }
|
---|
| 86 | };
|
---|
| 87 |
|
---|
| 88 | class FindTrCalls: protected AST::Visitor
|
---|
| 89 | {
|
---|
| 90 | public:
|
---|
| 91 | void operator()(Translator *translator, const QString &fileName, AST::Node *node)
|
---|
| 92 | {
|
---|
| 93 | m_translator = translator;
|
---|
| 94 | m_fileName = fileName;
|
---|
| 95 | m_component = QFileInfo(fileName).baseName(); //matches qsTr usage in QScriptEngine
|
---|
| 96 | accept(node);
|
---|
| 97 | }
|
---|
| 98 |
|
---|
| 99 | QList<Comment> comments;
|
---|
| 100 |
|
---|
| 101 | protected:
|
---|
| 102 | using AST::Visitor::visit;
|
---|
| 103 | using AST::Visitor::endVisit;
|
---|
| 104 |
|
---|
| 105 | void accept(AST::Node *node)
|
---|
| 106 | { AST::Node::acceptChild(node, this); }
|
---|
| 107 |
|
---|
| 108 | virtual void endVisit(AST::CallExpression *node)
|
---|
| 109 | {
|
---|
| 110 | m_bSource.clear();
|
---|
| 111 | if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(node->base)) {
|
---|
| 112 | if (idExpr->name->asString() == QLatin1String("qsTr") ||
|
---|
| 113 | idExpr->name->asString() == QLatin1String("QT_TR_NOOP")) {
|
---|
| 114 | if (!node->arguments)
|
---|
| 115 | return;
|
---|
| 116 | AST::BinaryExpression *binary = AST::cast<AST::BinaryExpression *>(node->arguments->expression);
|
---|
| 117 | if (binary) {
|
---|
| 118 | if (!createString(binary))
|
---|
| 119 | m_bSource.clear();
|
---|
| 120 | }
|
---|
| 121 | AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
|
---|
| 122 | if (literal || !m_bSource.isEmpty()) {
|
---|
| 123 | const QString source = literal ? literal->value->asString() : m_bSource;
|
---|
| 124 |
|
---|
| 125 | QString comment;
|
---|
| 126 | bool plural = false;
|
---|
| 127 | AST::ArgumentList *commentNode = node->arguments->next;
|
---|
| 128 | if (commentNode && AST::cast<AST::StringLiteral *>(commentNode->expression)) {
|
---|
| 129 | literal = AST::cast<AST::StringLiteral *>(commentNode->expression);
|
---|
| 130 | comment = literal->value->asString();
|
---|
| 131 |
|
---|
| 132 | AST::ArgumentList *nNode = commentNode->next;
|
---|
| 133 | if (nNode)
|
---|
| 134 | plural = true;
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | QString id;
|
---|
| 138 | QString extracomment;
|
---|
| 139 | TranslatorMessage::ExtraData extra;
|
---|
| 140 | Comment scomment = findComment(node->firstSourceLocation().startLine);
|
---|
| 141 | if (scomment.isValid()) {
|
---|
| 142 | extracomment = scomment.extracomment;
|
---|
| 143 | extra = scomment.extra;
|
---|
| 144 | id = scomment.msgid;
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | TranslatorMessage msg(m_component, source,
|
---|
| 148 | comment, QString(), m_fileName,
|
---|
| 149 | node->firstSourceLocation().startLine, QStringList(),
|
---|
| 150 | TranslatorMessage::Unfinished, plural);
|
---|
| 151 | msg.setExtraComment(extracomment.simplified());
|
---|
| 152 | msg.setId(id);
|
---|
| 153 | msg.setExtras(extra);
|
---|
| 154 | m_translator->extend(msg);
|
---|
| 155 | }
|
---|
| 156 | } else if (idExpr->name->asString() == QLatin1String("qsTranslate") ||
|
---|
| 157 | idExpr->name->asString() == QLatin1String("QT_TRANSLATE_NOOP")) {
|
---|
| 158 | if (node->arguments && AST::cast<AST::StringLiteral *>(node->arguments->expression)) {
|
---|
| 159 | AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
|
---|
| 160 | const QString context = literal->value->asString();
|
---|
| 161 |
|
---|
| 162 | QString source;
|
---|
| 163 | QString comment;
|
---|
| 164 | bool plural = false;
|
---|
| 165 | AST::ArgumentList *sourceNode = node->arguments->next;
|
---|
| 166 | if (!sourceNode)
|
---|
| 167 | return;
|
---|
| 168 | literal = AST::cast<AST::StringLiteral *>(sourceNode->expression);
|
---|
| 169 | AST::BinaryExpression *binary = AST::cast<AST::BinaryExpression *>(sourceNode->expression);
|
---|
| 170 | if (binary) {
|
---|
| 171 | if (!createString(binary))
|
---|
| 172 | m_bSource.clear();
|
---|
| 173 | }
|
---|
| 174 | if (!literal && m_bSource.isEmpty())
|
---|
| 175 | return;
|
---|
| 176 |
|
---|
| 177 | QString id;
|
---|
| 178 | QString extracomment;
|
---|
| 179 | TranslatorMessage::ExtraData extra;
|
---|
| 180 | Comment scomment = findComment(node->firstSourceLocation().startLine);
|
---|
| 181 | if (scomment.isValid()) {
|
---|
| 182 | extracomment = scomment.extracomment;
|
---|
| 183 | extra = scomment.extra;
|
---|
| 184 | id = scomment.msgid;
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | source = literal ? literal->value->asString() : m_bSource;
|
---|
| 188 | AST::ArgumentList *commentNode = sourceNode->next;
|
---|
| 189 | if (commentNode && AST::cast<AST::StringLiteral *>(commentNode->expression)) {
|
---|
| 190 | literal = AST::cast<AST::StringLiteral *>(commentNode->expression);
|
---|
| 191 | comment = literal->value->asString();
|
---|
| 192 |
|
---|
| 193 | AST::ArgumentList *nNode = commentNode->next;
|
---|
| 194 | if (nNode)
|
---|
| 195 | plural = true;
|
---|
| 196 | }
|
---|
| 197 |
|
---|
| 198 | TranslatorMessage msg(context, source,
|
---|
| 199 | comment, QString(), m_fileName,
|
---|
| 200 | node->firstSourceLocation().startLine, QStringList(),
|
---|
| 201 | TranslatorMessage::Unfinished, plural);
|
---|
| 202 | msg.setExtraComment(extracomment.simplified());
|
---|
| 203 | msg.setId(id);
|
---|
| 204 | msg.setExtras(extra);
|
---|
| 205 | m_translator->extend(msg);
|
---|
| 206 | }
|
---|
| 207 | } else if (idExpr->name->asString() == QLatin1String("qsTrId") ||
|
---|
| 208 | idExpr->name->asString() == QLatin1String("QT_TRID_NOOP")) {
|
---|
| 209 | if (!node->arguments)
|
---|
| 210 | return;
|
---|
| 211 |
|
---|
| 212 | AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression);
|
---|
| 213 | if (literal) {
|
---|
| 214 |
|
---|
| 215 | QString extracomment;
|
---|
| 216 | QString sourcetext;
|
---|
| 217 | TranslatorMessage::ExtraData extra;
|
---|
| 218 | Comment comment = findComment(node->firstSourceLocation().startLine);
|
---|
| 219 | if (comment.isValid()) {
|
---|
| 220 | extracomment = comment.extracomment;
|
---|
| 221 | sourcetext = comment.sourcetext;
|
---|
| 222 | extra = comment.extra;
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | const QString id = literal->value->asString();
|
---|
| 226 | bool plural = node->arguments->next;
|
---|
| 227 |
|
---|
| 228 | TranslatorMessage msg(QString(), sourcetext,
|
---|
| 229 | QString(), QString(), m_fileName,
|
---|
| 230 | node->firstSourceLocation().startLine, QStringList(),
|
---|
| 231 | TranslatorMessage::Unfinished, plural);
|
---|
| 232 | msg.setExtraComment(extracomment.simplified());
|
---|
| 233 | msg.setId(id);
|
---|
| 234 | msg.setExtras(extra);
|
---|
| 235 | m_translator->extend(msg);
|
---|
| 236 | }
|
---|
| 237 | }
|
---|
| 238 | }
|
---|
| 239 | }
|
---|
| 240 |
|
---|
| 241 | private:
|
---|
| 242 | bool createString(AST::BinaryExpression *b)
|
---|
| 243 | {
|
---|
| 244 | if (!b || b->op != 0)
|
---|
| 245 | return false;
|
---|
| 246 | AST::BinaryExpression *l = AST::cast<AST::BinaryExpression *>(b->left);
|
---|
| 247 | AST::BinaryExpression *r = AST::cast<AST::BinaryExpression *>(b->right);
|
---|
| 248 | AST::StringLiteral *ls = AST::cast<AST::StringLiteral *>(b->left);
|
---|
| 249 | AST::StringLiteral *rs = AST::cast<AST::StringLiteral *>(b->right);
|
---|
| 250 | if ((!l && !ls) || (!r && !rs))
|
---|
| 251 | return false;
|
---|
| 252 | if (l) {
|
---|
| 253 | if (!createString(l))
|
---|
| 254 | return false;
|
---|
| 255 | } else
|
---|
| 256 | m_bSource.prepend(ls->value->asString());
|
---|
| 257 |
|
---|
| 258 | if (r) {
|
---|
| 259 | if (!createString(r))
|
---|
| 260 | return false;
|
---|
| 261 | } else
|
---|
| 262 | m_bSource.append(rs->value->asString());
|
---|
| 263 |
|
---|
| 264 | return true;
|
---|
| 265 | }
|
---|
| 266 |
|
---|
| 267 | Comment findComment(int loc)
|
---|
| 268 | {
|
---|
| 269 | if (comments.isEmpty())
|
---|
| 270 | return Comment();
|
---|
| 271 |
|
---|
| 272 | int i = 0;
|
---|
| 273 | int commentLoc = comments.at(i).lastLine;
|
---|
| 274 | while (commentLoc <= loc) {
|
---|
| 275 | if (commentLoc == loc)
|
---|
| 276 | return comments.at(i);
|
---|
| 277 | if (i == comments.count()-1)
|
---|
| 278 | break;
|
---|
| 279 | commentLoc = comments.at(++i).lastLine;
|
---|
| 280 | }
|
---|
| 281 | return Comment();
|
---|
| 282 | }
|
---|
| 283 |
|
---|
| 284 | Translator *m_translator;
|
---|
| 285 | QString m_fileName;
|
---|
| 286 | QString m_component;
|
---|
| 287 | QString m_bSource;
|
---|
| 288 | };
|
---|
| 289 |
|
---|
| 290 | QString createErrorString(const QString &filename, const QString &code, Parser &parser)
|
---|
| 291 | {
|
---|
| 292 | // print out error
|
---|
| 293 | QStringList lines = code.split(QLatin1Char('\n'));
|
---|
| 294 | lines.append(QLatin1String("\n")); // sentinel.
|
---|
| 295 | QString errorString;
|
---|
| 296 |
|
---|
| 297 | foreach (const DiagnosticMessage &m, parser.diagnosticMessages()) {
|
---|
| 298 |
|
---|
| 299 | if (m.isWarning())
|
---|
| 300 | continue;
|
---|
| 301 |
|
---|
| 302 | QString error = filename + QLatin1Char(':') + QString::number(m.loc.startLine)
|
---|
| 303 | + QLatin1Char(':') + QString::number(m.loc.startColumn) + QLatin1String(": error: ")
|
---|
| 304 | + m.message + QLatin1Char('\n');
|
---|
| 305 |
|
---|
| 306 | int line = 0;
|
---|
| 307 | if (m.loc.startLine > 0)
|
---|
| 308 | line = m.loc.startLine - 1;
|
---|
| 309 |
|
---|
| 310 | const QString textLine = lines.at(line);
|
---|
| 311 |
|
---|
| 312 | error += textLine + QLatin1Char('\n');
|
---|
| 313 |
|
---|
| 314 | int column = m.loc.startColumn - 1;
|
---|
| 315 | if (column < 0)
|
---|
| 316 | column = 0;
|
---|
| 317 |
|
---|
| 318 | column = qMin(column, textLine.length());
|
---|
| 319 |
|
---|
| 320 | for (int i = 0; i < column; ++i) {
|
---|
| 321 | const QChar ch = textLine.at(i);
|
---|
| 322 | if (ch.isSpace())
|
---|
| 323 | error += ch.unicode();
|
---|
| 324 | else
|
---|
| 325 | error += QLatin1Char(' ');
|
---|
| 326 | }
|
---|
| 327 | error += QLatin1String("^\n");
|
---|
| 328 | errorString += error;
|
---|
| 329 | }
|
---|
| 330 | return errorString;
|
---|
| 331 | }
|
---|
| 332 |
|
---|
| 333 | bool processComment(const QChar *chars, int length, Comment &comment)
|
---|
| 334 | {
|
---|
| 335 | // Try to match the logic of the QtScript parser.
|
---|
| 336 | if (!length)
|
---|
| 337 | return comment.isValid();
|
---|
| 338 | if (*chars == QLatin1Char(':') && chars[1].isSpace()) {
|
---|
| 339 | comment.extracomment += QString(chars+1, length-1);
|
---|
| 340 | } else if (*chars == QLatin1Char('=') && chars[1].isSpace()) {
|
---|
| 341 | comment.msgid = QString(chars+2, length-2).simplified();
|
---|
| 342 | } else if (*chars == QLatin1Char('~') && chars[1].isSpace()) {
|
---|
| 343 | QString text = QString(chars+2, length-2).trimmed();
|
---|
| 344 | int k = text.indexOf(QLatin1Char(' '));
|
---|
| 345 | if (k > -1)
|
---|
| 346 | comment.extra.insert(text.left(k), text.mid(k + 1).trimmed());
|
---|
| 347 | } else if (*chars == QLatin1Char('%') && chars[1].isSpace()) {
|
---|
| 348 | comment.sourcetext.reserve(comment.sourcetext.length() + length-2);
|
---|
| 349 | ushort *ptr = (ushort *)comment.sourcetext.data() + comment.sourcetext.length();
|
---|
| 350 | int p = 2, c;
|
---|
| 351 | forever {
|
---|
| 352 | if (p >= length)
|
---|
| 353 | break;
|
---|
| 354 | c = chars[p++].unicode();
|
---|
| 355 | if (isspace(c))
|
---|
| 356 | continue;
|
---|
| 357 | if (c != '"')
|
---|
| 358 | break;
|
---|
| 359 | forever {
|
---|
| 360 | if (p >= length)
|
---|
| 361 | break;
|
---|
| 362 | c = chars[p++].unicode();
|
---|
| 363 | if (c == '"')
|
---|
| 364 | break;
|
---|
| 365 | if (c == '\\') {
|
---|
| 366 | if (p >= length)
|
---|
| 367 | break;
|
---|
| 368 | c = chars[p++].unicode();
|
---|
| 369 | if (c == '\n')
|
---|
| 370 | break;
|
---|
| 371 | *ptr++ = '\\';
|
---|
| 372 | }
|
---|
| 373 | *ptr++ = c;
|
---|
| 374 | }
|
---|
| 375 | }
|
---|
| 376 | comment.sourcetext.resize(ptr - (ushort *)comment.sourcetext.data());
|
---|
| 377 | }
|
---|
| 378 | return comment.isValid();
|
---|
| 379 | }
|
---|
| 380 |
|
---|
| 381 | bool loadQml(Translator &translator, const QString &filename, ConversionData &cd)
|
---|
| 382 | {
|
---|
| 383 | cd.m_sourceFileName = filename;
|
---|
| 384 | QFile file(filename);
|
---|
| 385 | if (!file.open(QIODevice::ReadOnly)) {
|
---|
| 386 | cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString()));
|
---|
| 387 | return false;
|
---|
| 388 | }
|
---|
| 389 |
|
---|
| 390 | const QString code = QTextStream(&file).readAll();
|
---|
| 391 |
|
---|
| 392 | Engine driver;
|
---|
| 393 | Parser parser(&driver);
|
---|
| 394 |
|
---|
| 395 | NodePool nodePool(filename, &driver);
|
---|
| 396 | driver.setNodePool(&nodePool);
|
---|
| 397 |
|
---|
| 398 | Lexer lexer(&driver);
|
---|
| 399 | lexer.setCode(code, /*line = */ 1);
|
---|
| 400 | driver.setLexer(&lexer);
|
---|
| 401 |
|
---|
| 402 | if (parser.parse()) {
|
---|
| 403 | FindTrCalls trCalls;
|
---|
| 404 |
|
---|
| 405 | // build up a list of comments that contain translation information.
|
---|
| 406 | for (int i = 0; i < driver.comments().size(); ++i) {
|
---|
| 407 | AST::SourceLocation loc = driver.comments().at(i);
|
---|
| 408 | QString commentStr = code.mid(loc.offset, loc.length);
|
---|
| 409 |
|
---|
| 410 | if (trCalls.comments.isEmpty() || trCalls.comments.last().lastLine != int(loc.startLine)) {
|
---|
| 411 | Comment comment;
|
---|
| 412 | comment.lastLine = loc.startLine+1;
|
---|
| 413 | if (processComment(commentStr.constData(), commentStr.length(), comment))
|
---|
| 414 | trCalls.comments.append(comment);
|
---|
| 415 | } else {
|
---|
| 416 | Comment &lastComment = trCalls.comments.last();
|
---|
| 417 | lastComment.lastLine += 1;
|
---|
| 418 | processComment(commentStr.constData(), commentStr.length(), lastComment);
|
---|
| 419 | }
|
---|
| 420 | }
|
---|
| 421 |
|
---|
| 422 | //find all tr calls in the code
|
---|
| 423 | trCalls(&translator, filename, parser.ast());
|
---|
| 424 | } else {
|
---|
| 425 | QString error = createErrorString(filename, code, parser);
|
---|
| 426 | cd.appendError(error);
|
---|
| 427 | return false;
|
---|
| 428 | }
|
---|
| 429 | return true;
|
---|
| 430 | }
|
---|
| 431 |
|
---|
| 432 | QT_END_NAMESPACE
|
---|