/**************************************************************************** ** ** 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$ ** GNU Lesser General Public License Usage ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "lupdate.h" #include #include #include #include #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 #include #include #include #include #include #include #include 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 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(node->base)) { if (idExpr->name->asString() == QLatin1String("qsTr") || idExpr->name->asString() == QLatin1String("QT_TR_NOOP")) { if (!node->arguments) return; AST::BinaryExpression *binary = AST::cast(node->arguments->expression); if (binary) { if (!createString(binary)) m_bSource.clear(); } AST::StringLiteral *literal = AST::cast(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(commentNode->expression)) { literal = AST::cast(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(node->arguments->expression)) { AST::StringLiteral *literal = AST::cast(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(sourceNode->expression); AST::BinaryExpression *binary = AST::cast(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(commentNode->expression)) { literal = AST::cast(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(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(b->left); AST::BinaryExpression *r = AST::cast(b->right); AST::StringLiteral *ls = AST::cast(b->left); AST::StringLiteral *rs = AST::cast(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