diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /tools/linguist/shared | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'tools/linguist/shared')
30 files changed, 15020 insertions, 0 deletions
diff --git a/tools/linguist/shared/abstractproitemvisitor.h b/tools/linguist/shared/abstractproitemvisitor.h new file mode 100644 index 0000000..b108e7e --- /dev/null +++ b/tools/linguist/shared/abstractproitemvisitor.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ABSTRACTPROITEMVISITOR +#define ABSTRACTPROITEMVISITOR + +#include "proitems.h" + +QT_BEGIN_NAMESPACE + +struct AbstractProItemVisitor +{ + virtual ~AbstractProItemVisitor() {} + virtual bool visitBeginProBlock(ProBlock *block) = 0; + virtual bool visitEndProBlock(ProBlock *block) = 0; + + virtual bool visitBeginProVariable(ProVariable *variable) = 0; + virtual bool visitEndProVariable(ProVariable *variable) = 0; + + virtual bool visitBeginProFile(ProFile *value) = 0; + virtual bool visitEndProFile(ProFile *value) = 0; + + virtual bool visitProValue(ProValue *value) = 0; + virtual bool visitProFunction(ProFunction *function) = 0; + virtual bool visitProOperator(ProOperator *function) = 0; + virtual bool visitProCondition(ProCondition *function) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTPROITEMVISITOR + diff --git a/tools/linguist/shared/cpp.cpp b/tools/linguist/shared/cpp.cpp new file mode 100644 index 0000000..28616cc --- /dev/null +++ b/tools/linguist/shared/cpp.cpp @@ -0,0 +1,1074 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QStack> +#include <QtCore/QString> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <ctype.h> // for isXXX() + +QT_BEGIN_NAMESPACE + +/* qmake ignore Q_OBJECT */ + +static const char MagicComment[] = "TRANSLATOR "; + +static QSet<QString> needs_Q_OBJECT; +static QSet<QString> lacks_Q_OBJECT; + +static const int yyIdentMaxLen = 128; +static const int yyCommentMaxLen = 65536; +static const int yyStringMaxLen = 65536; + +#define STRINGIFY_INTERNAL(x) #x +#define STRINGIFY(x) STRINGIFY_INTERNAL(x) +#define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s))) + +//#define DIAGNOSE_RETRANSLATABILITY +/* + The first part of this source file is the C++ tokenizer. We skip + most of C++; the only tokens that interest us are defined here. + Thus, the code fragment + + int main() + { + printf("Hello, world!\n"); + return 0; + } + + is broken down into the following tokens (Tok_ omitted): + + Ident Ident LeftParen RightParen + LeftBrace + Ident LeftParen String RightParen Semicolon + return Semicolon + RightBrace. + + The 0 doesn't produce any token. +*/ + +enum { + Tok_Eof, Tok_class, Tok_namespace, Tok_return, + Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8, + Tok_Q_OBJECT = 20, Tok_Q_DECLARE_TR_FUNCTIONS, + Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon, + Tok_Equals, + Tok_LeftBrace = 30, Tok_RightBrace, Tok_LeftParen, Tok_RightParen, Tok_Comma, Tok_Semicolon, + Tok_Integer = 40, + Tok_Other +}; + +/* + The tokenizer maintains the following global variables. The names + should be self-explanatory. +*/ +static QString yyFileName; +static int yyCh; +static bool yyCodecIsUtf8; +static bool yyForceUtf8; +static QString yyIdent; +static QString yyComment; +static QString yyString; +static qlonglong yyInteger; +static QStack<int> yySavedBraceDepth; +static QStack<int> yySavedParenDepth; +static int yyBraceDepth; +static int yyParenDepth; +static int yyLineNo; +static int yyCurLineNo; +static int yyBraceLineNo; +static int yyParenLineNo; +static bool yyTokColonSeen = false; + +// the string to read from and current position in the string +static QTextCodec *yySourceCodec; +static bool yySourceIsUnicode; +static QString yyInStr; +static int yyInPos; + +static uint getChar() +{ + if (yyInPos >= yyInStr.size()) + return EOF; + QChar c = yyInStr[yyInPos++]; + if (c.unicode() == '\n') + ++yyCurLineNo; + return c.unicode(); +} + +static uint getToken() +{ + yyIdent.clear(); + yyComment.clear(); + yyString.clear(); + + while (yyCh != EOF) { + yyLineNo = yyCurLineNo; + + if (isalpha(yyCh) || yyCh == '_') { + do { + yyIdent += yyCh; + yyCh = getChar(); + } while (isalnum(yyCh) || yyCh == '_'); + + //qDebug() << "IDENT: " << yyIdent; + + switch (yyIdent.at(0).unicode()) { + case 'Q': + if (yyIdent == QLatin1String("Q_OBJECT")) + return Tok_Q_OBJECT; + if (yyIdent == QLatin1String("Q_DECLARE_TR_FUNCTIONS")) + return Tok_Q_DECLARE_TR_FUNCTIONS; + if (yyIdent == QLatin1String("QT_TR_NOOP")) + return Tok_tr; + if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP")) + return Tok_translate; + if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP3")) + return Tok_translate; + if (yyIdent == QLatin1String("QT_TR_NOOP_UTF8")) + return Tok_trUtf8; + if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP_UTF8")) + return Tok_translateUtf8; + if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP3_UTF8")) + return Tok_translateUtf8; + break; + case 'T': + // TR() for when all else fails + if (yyIdent.compare(QLatin1String("TR"), Qt::CaseInsensitive) == 0) { + return Tok_tr; + } + break; + case 'c': + if (yyIdent == QLatin1String("class")) + return Tok_class; + break; + case 'f': + /* + QTranslator::findMessage() has the same parameters as + QApplication::translate(). + */ + if (yyIdent == QLatin1String("findMessage")) + return Tok_translate; + break; + case 'n': + if (yyIdent == QLatin1String("namespace")) + return Tok_namespace; + break; + case 'r': + if (yyIdent == QLatin1String("return")) + return Tok_return; + break; + case 's': + if (yyIdent == QLatin1String("struct")) + return Tok_class; + break; + case 't': + if (yyIdent == QLatin1String("tr")) { + return Tok_tr; + } + if (yyIdent == QLatin1String("trUtf8")) { + return Tok_trUtf8; + } + if (yyIdent == QLatin1String("translate")) { + return Tok_translate; + } + } + return Tok_Ident; + } else { + switch (yyCh) { + case '#': + /* + Early versions of lupdate complained about + unbalanced braces in the following code: + + #ifdef ALPHA + while (beta) { + #else + while (gamma) { + #endif + delta; + } + + The code contains, indeed, two opening braces for + one closing brace; yet there's no reason to panic. + + The solution is to remember yyBraceDepth as it was + when #if, #ifdef or #ifndef was met, and to set + yyBraceDepth to that value when meeting #elif or + #else. + */ + do { + yyCh = getChar(); + } while (isspace(yyCh) && yyCh != '\n'); + + switch (yyCh) { + case 'i': + yyCh = getChar(); + if (yyCh == 'f') { + // if, ifdef, ifndef + yySavedBraceDepth.push(yyBraceDepth); + yySavedParenDepth.push(yyParenDepth); + } + break; + case 'e': + yyCh = getChar(); + if (yyCh == 'l') { + // elif, else + if (!yySavedBraceDepth.isEmpty()) { + yyBraceDepth = yySavedBraceDepth.top(); + yyParenDepth = yySavedParenDepth.top(); + } + } else if (yyCh == 'n') { + // endif + if (!yySavedBraceDepth.isEmpty()) { + yySavedBraceDepth.pop(); + yySavedParenDepth.pop(); + } + } + } + while (isalnum(yyCh) || yyCh == '_') + yyCh = getChar(); + break; + case '/': + yyCh = getChar(); + if (yyCh == '/') { + do { + yyCh = getChar(); + if (yyCh == EOF) + break; + yyComment.append(yyCh); + } while (yyCh != '\n'); + } else if (yyCh == '*') { + bool metAster = false; + bool metAsterSlash = false; + + while (!metAsterSlash) { + yyCh = getChar(); + if (yyCh == EOF) { + qWarning("%s: Unterminated C++ comment starting at" + " line %d\n", + qPrintable(yyFileName), yyLineNo); + return Tok_Comment; + } + yyComment.append(yyCh); + + if (yyCh == '*') + metAster = true; + else if (metAster && yyCh == '/') + metAsterSlash = true; + else + metAster = false; + } + yyCh = getChar(); + yyComment.chop(2); + } + return Tok_Comment; + case '"': + yyCh = getChar(); + while (yyCh != EOF && yyCh != '\n' && yyCh != '"') { + if (yyCh == '\\') { + yyCh = getChar(); + if (yyString.size() < yyStringMaxLen) { + yyString.append(QLatin1Char('\\')); + yyString.append(yyCh); + } + } else { + if (yyString.size() < yyStringMaxLen) + yyString.append(yyCh); + } + yyCh = getChar(); + } + + if (yyCh != '"') + qWarning("%s:%d: Unterminated C++ string", + qPrintable(yyFileName), yyLineNo); + + if (yyCh == EOF) + return Tok_Eof; + yyCh = getChar(); + return Tok_String; + case '-': + yyCh = getChar(); + if (yyCh == '>') { + yyCh = getChar(); + return Tok_Arrow; + } + break; + case ':': + yyCh = getChar(); + if (yyCh == ':') { + yyCh = getChar(); + return Tok_ColonColon; + } + return Tok_Colon; + // Incomplete: '<' might be part of '<=' or of template syntax. + // The main intent of not completely ignoring it is to break + // parsing of things like std::cout << QObject::tr() as + // context std::cout::QObject (see Task 161106) + case '=': + yyCh = getChar(); + return Tok_Equals; + case '>': + case '<': + yyCh = getChar(); + return Tok_Other; + case '\'': + yyCh = getChar(); + if (yyCh == '\\') + yyCh = getChar(); + + do { + yyCh = getChar(); + } while (yyCh != EOF && yyCh != '\''); + yyCh = getChar(); + break; + case '{': + if (yyBraceDepth == 0) + yyBraceLineNo = yyCurLineNo; + yyBraceDepth++; + yyCh = getChar(); + return Tok_LeftBrace; + case '}': + if (yyBraceDepth == 0) + yyBraceLineNo = yyCurLineNo; + yyBraceDepth--; + yyCh = getChar(); + return Tok_RightBrace; + case '(': + if (yyParenDepth == 0) + yyParenLineNo = yyCurLineNo; + yyParenDepth++; + yyCh = getChar(); + return Tok_LeftParen; + case ')': + if (yyParenDepth == 0) + yyParenLineNo = yyCurLineNo; + yyParenDepth--; + yyCh = getChar(); + return Tok_RightParen; + case ',': + yyCh = getChar(); + return Tok_Comma; + case ';': + yyCh = getChar(); + return Tok_Semicolon; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + QByteArray ba; + ba += yyCh; + yyCh = getChar(); + bool hex = yyCh == 'x'; + if (hex) { + ba += yyCh; + yyCh = getChar(); + } + while (hex ? isxdigit(yyCh) : isdigit(yyCh)) { + ba += yyCh; + yyCh = getChar(); + } + bool ok; + yyInteger = ba.toLongLong(&ok); + if (ok) + return Tok_Integer; + break; + } + default: + yyCh = getChar(); + break; + } + } + } + return Tok_Eof; +} + +/* + The second part of this source file is the parser. It accomplishes + a very easy task: It finds all strings inside a tr() or translate() + call, and possibly finds out the context of the call. It supports + three cases: (1) the context is specified, as in + FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello"); + (2) the call appears within an inlined function; (3) the call + appears within a function defined outside the class definition. +*/ + +static uint yyTok; + +static bool match(uint t) +{ + bool matches = (yyTok == t); + if (matches) + yyTok = getToken(); + return matches; +} + +static bool matchString(QString *s) +{ + bool matches = (yyTok == Tok_String); + s->clear(); + while (yyTok == Tok_String) { + *s += yyString; + yyTok = getToken(); + } + return matches; +} + +static bool matchEncoding(bool *utf8) +{ + STRING(QApplication); + STRING(QCoreApplication); + STRING(UnicodeUTF8); + STRING(DefaultCodec); + STRING(CodecForTr); + + if (yyTok != Tok_Ident) + return false; + if (yyIdent == strQApplication || yyIdent == strQCoreApplication) { + yyTok = getToken(); + if (yyTok == Tok_ColonColon) + yyTok = getToken(); + } + if (yyIdent == strUnicodeUTF8) { + *utf8 = true; + yyTok = getToken(); + return true; + } + if (yyIdent == strDefaultCodec || yyIdent == strCodecForTr) { + *utf8 = false; + yyTok = getToken(); + return true; + } + return false; +} + +static bool matchInteger(qlonglong *number) +{ + bool matches = (yyTok == Tok_Integer); + if (matches) { + yyTok = getToken(); + *number = yyInteger; + } + return matches; +} + +static bool matchStringOrNull(QString *s) +{ + bool matches = matchString(s); + qlonglong num = 0; + if (!matches) + matches = matchInteger(&num); + return matches && num == 0; +} + +/* + * match any expression that can return a number, which can be + * 1. Literal number (e.g. '11') + * 2. simple identifier (e.g. 'm_count') + * 3. simple function call (e.g. 'size()' ) + * 4. function call on an object (e.g. 'list.size()') + * 5. function call on an object (e.g. 'list->size()') + * + * Other cases: + * size(2,4) + * list().size() + * list(a,b).size(2,4) + * etc... + */ +static bool matchExpression() +{ + if (match(Tok_Integer)) + return true; + + int parenlevel = 0; + while (match(Tok_Ident) || parenlevel > 0) { + if (yyTok == Tok_RightParen) { + if (parenlevel == 0) break; + --parenlevel; + yyTok = getToken(); + } else if (yyTok == Tok_LeftParen) { + yyTok = getToken(); + if (yyTok == Tok_RightParen) { + yyTok = getToken(); + } else { + ++parenlevel; + } + } else if (yyTok == Tok_Ident) { + continue; + } else if (yyTok == Tok_Arrow) { + yyTok = getToken(); + } else if (parenlevel == 0) { + return false; + } + } + return true; +} + +static QStringList resolveNamespaces( + const QStringList &namespaces, const QHash<QString, QStringList> &namespaceAliases) +{ + static QString strColons(QLatin1String("::")); + + QStringList ns; + foreach (const QString &cns, namespaces) { + ns << cns; + ns = namespaceAliases.value(ns.join(strColons), ns); + } + return ns; +} + +static QStringList getFullyQualifiedNamespaceName( + const QSet<QString> &allNamespaces, const QStringList &namespaces, + const QHash<QString, QStringList> &namespaceAliases, + const QStringList &segments) +{ + static QString strColons(QLatin1String("::")); + + if (segments.first().isEmpty()) { + // fully qualified + QStringList segs = segments; + segs.removeFirst(); + return resolveNamespaces(segs, namespaceAliases); + } else { + for (int n = namespaces.count(); --n >= -1; ) { + QStringList ns; + for (int i = 0; i <= n; ++i) // Note: n == -1 possible + ns << namespaces[i]; + foreach (const QString &cns, segments) { + ns << cns; + ns = namespaceAliases.value(ns.join(strColons), ns); + } + if (allNamespaces.contains(ns.join(strColons))) + return ns; + } + + // Fallback when the namespace was declared in a header, etc. + QStringList ns = namespaces; + ns += segments; + return ns; + } +} + +static QString getFullyQualifiedClassName( + const QSet<QString> &allClasses, const QStringList &namespaces, + const QHash<QString, QStringList> &namespaceAliases, + const QString &ident, bool hasPrefix) +{ + static QString strColons(QLatin1String("::")); + + QString context = ident; + QStringList segments = context.split(strColons); + if (segments.first().isEmpty()) { + // fully qualified + segments.removeFirst(); + context = resolveNamespaces(segments, namespaceAliases).join(strColons); + } else { + for (int n = namespaces.count(); --n >= -1; ) { + QStringList ns; + for (int i = 0; i <= n; ++i) // Note: n == -1 possible + ns.append(namespaces[i]); + foreach (const QString &cns, segments) { + ns.append(cns); + ns = namespaceAliases.value(ns.join(strColons), ns); + } + QString nctx = ns.join(strColons); + if (allClasses.contains(nctx)) { + context = nctx; + goto gotit; + } + } + + if (!hasPrefix && namespaces.count()) + context = namespaces.join(strColons) + strColons + context; + } +gotit: + //qDebug() << "CLASSES:" << allClasses << "NAMEPACES:" << namespaces + // << "IDENT:" << ident << "CONTEXT:" << context; + return context; +} + + +static QString transcode(const QString &str, bool utf8) +{ + static const char tab[] = "abfnrtv"; + static const char backTab[] = "\a\b\f\n\r\t\v"; + const QString in = (!utf8 || yySourceIsUnicode) + ? str : QString::fromUtf8(yySourceCodec->fromUnicode(str).data()); + QString out; + + out.reserve(in.length()); + for (int i = 0; i < in.length();) { + ushort c = in[i++].unicode(); + if (c == '\\') { + if (i >= in.length()) + break; + c = in[i++].unicode(); + + if (c == '\n') + continue; + + if (c == 'x') { + QByteArray hex; + while (i < in.length() && isxdigit((c = in[i].unicode()))) { + hex += c; + i++; + } + out += hex.toUInt(0, 16); + } else if (c >= '0' && c < '8') { + QByteArray oct; + int n = 0; + oct += c; + while (n < 2 && i < in.length() && (c = in[i].unicode()) >= '0' && c < '8') { + i++; + n++; + oct += c; + } + out += oct.toUInt(0, 8); + } else { + const char *p = strchr(tab, c); + out += QChar(QLatin1Char(!p ? c : backTab[p - tab])); + } + } else { + out += c; + } + } + return out; +} + +static void recordMessage( + Translator *tor, int line, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, bool utf8, bool plural) +{ + TranslatorMessage msg( + transcode(context, utf8), transcode(text, utf8), transcode(comment, utf8), QString(), + yyFileName, line, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(transcode(extracomment.simplified(), utf8)); + if ((utf8 || yyForceUtf8) && !yyCodecIsUtf8 && msg.needs8Bit()) + msg.setUtf8(true); + tor->extend(msg); +} + +static void parse(Translator *tor, const QString &initialContext, const QString &defaultContext) +{ + static QString strColons(QLatin1String("::")); + + QMap<QString, QString> qualifiedContexts; + QSet<QString> allClasses; + QSet<QString> allNamespaces; + QHash<QString, QStringList> namespaceAliases; + QStringList namespaces; + QString context; + QString text; + QString comment; + QString extracomment; + QString functionContext = initialContext; + QString prefix; +#ifdef DIAGNOSE_RETRANSLATABILITY + QString functionName; +#endif + int line; + bool utf8 = false; + bool missing_Q_OBJECT = false; + + yyTok = getToken(); + while (yyTok != Tok_Eof) { + //qDebug() << "TOKEN: " << yyTok; + switch (yyTok) { + case Tok_class: + yyTokColonSeen = false; + /* + Partial support for inlined functions. + */ + yyTok = getToken(); + if (yyBraceDepth == namespaces.count() && yyParenDepth == 0) { + QStringList fct; + do { + /* + This code should execute only once, but we play + safe with impure definitions such as + 'class Q_EXPORT QMessageBox', in which case + 'QMessageBox' is the class name, not 'Q_EXPORT'. + */ + fct = QStringList(yyIdent); + yyTok = getToken(); + } while (yyTok == Tok_Ident); + while (yyTok == Tok_ColonColon) { + yyTok = getToken(); + if (yyTok != Tok_Ident) + break; // Oops ... + fct += yyIdent; + yyTok = getToken(); + } + functionContext = resolveNamespaces(namespaces + fct, namespaceAliases).join(strColons); + allClasses.insert(functionContext); + + if (yyTok == Tok_Colon) { + missing_Q_OBJECT = true; + // Skip any token until '{' since lupdate might do things wrong if it finds + // a '::' token here. + do { + yyTok = getToken(); + } while (yyTok != Tok_LeftBrace && yyTok != Tok_Eof); + } else { + //functionContext = defaultContext; + } + } + break; + case Tok_namespace: + yyTokColonSeen = false; + yyTok = getToken(); + if (yyTok == Tok_Ident) { + QString ns = yyIdent; + yyTok = getToken(); + if (yyTok == Tok_LeftBrace) { + if (yyBraceDepth == namespaces.count() + 1) { + namespaces.append(ns); + allNamespaces.insert(namespaces.join(strColons)); + } + } else if (yyTok == Tok_Equals) { + // e.g. namespace Is = OuterSpace::InnerSpace; + QStringList alias = namespaces; + alias.append(ns); + QStringList fullName; + yyTok = getToken(); + if (yyTok == Tok_ColonColon) + fullName.append(QString()); + while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) { + if (yyTok == Tok_Ident) + fullName.append(yyIdent); + yyTok = getToken(); + } + namespaceAliases[alias.join(strColons)] = + getFullyQualifiedNamespaceName(allNamespaces, namespaces, namespaceAliases, fullName); + } + } + break; + case Tok_tr: + case Tok_trUtf8: + utf8 = (yyTok == Tok_trUtf8); + line = yyLineNo; + yyTok = getToken(); + if (match(Tok_LeftParen) && matchString(&text) && !text.isEmpty()) { + comment.clear(); + bool plural = false; + + if (match(Tok_RightParen)) { + // no comment + } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment + if (match(Tok_RightParen)) { + // ok, + } else if (match(Tok_Comma)) { + plural = true; + } + } + if (prefix.isEmpty()) { + context = functionContext; + } else { +#ifdef DIAGNOSE_RETRANSLATABILITY + int last = prefix.lastIndexOf(strColons); + QString className = prefix.mid(last == -1 ? 0 : last + 2); + if (!className.isEmpty() && className == functionName) { + qWarning("%s::%d: It is not recommended to call tr() from within a constructor '%s::%s' ", + qPrintable(yyFileName), yyLineNo, + className.constData(), functionName.constData()); + } +#endif + prefix.chop(2); + context = getFullyQualifiedClassName(allClasses, namespaces, namespaceAliases, prefix, true); + } + prefix.clear(); + if (qualifiedContexts.contains(context)) + context = qualifiedContexts[context]; + + if (!text.isEmpty()) + recordMessage(tor, line, context, text, comment, extracomment, utf8, plural); + + if (lacks_Q_OBJECT.contains(context)) { + qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro", + qPrintable(yyFileName), yyLineNo, + qPrintable(context)); + lacks_Q_OBJECT.remove(context); + } else { + needs_Q_OBJECT.insert(context); + } + } + extracomment.clear(); + break; + case Tok_translateUtf8: + case Tok_translate: + utf8 = (yyTok == Tok_translateUtf8); + line = yyLineNo; + yyTok = getToken(); + if (match(Tok_LeftParen) + && matchString(&context) + && match(Tok_Comma) + && matchString(&text)) + { + comment.clear(); + bool plural = false; + if (!match(Tok_RightParen)) { + // look for comment + if (match(Tok_Comma) && matchStringOrNull(&comment)) { + if (!match(Tok_RightParen)) { + // look for encoding + if (match(Tok_Comma)) { + if (matchEncoding(&utf8)) { + if (!match(Tok_RightParen)) { + // look for the plural quantifier, + // this can be a number, an identifier or + // a function call, + // so for simplicity we mark it as plural if + // we know we have a comma instead of an + // right parentheses. + plural = match(Tok_Comma); + } + } else { + // This can be a QTranslator::translate("context", + // "source", "comment", n) plural translation + if (matchExpression() && match(Tok_RightParen)) { + plural = true; + } else { + break; + } + } + } else { + break; + } + } + } else { + break; + } + } + if (!text.isEmpty()) + recordMessage(tor, line, context, text, comment, extracomment, utf8, plural); + } + extracomment.clear(); + break; + case Tok_Q_DECLARE_TR_FUNCTIONS: + case Tok_Q_OBJECT: + missing_Q_OBJECT = false; + yyTok = getToken(); + break; + case Tok_Ident: + prefix += yyIdent; + yyTok = getToken(); + if (yyTok != Tok_ColonColon) + prefix.clear(); + break; + case Tok_Comment: + if (yyComment.startsWith(QLatin1Char(':'))) { + yyComment.remove(0, 1); + extracomment.append(yyComment); + } else { + comment = yyComment.simplified(); + if (comment.startsWith(QLatin1String(MagicComment))) { + comment.remove(0, sizeof(MagicComment) - 1); + int k = comment.indexOf(QLatin1Char(' ')); + if (k == -1) { + context = comment; + } else { + context = comment.left(k); + comment.remove(0, k + 1); + recordMessage(tor, yyLineNo, context, QString(), comment, extracomment, false, false); + } + + /* + Provide a backdoor for people using "using + namespace". See the manual for details. + */ + k = 0; + while ((k = context.indexOf(strColons, k)) != -1) { + qualifiedContexts.insert(context.mid(k + 2), context); + k++; + } + } + } + yyTok = getToken(); + break; + case Tok_Arrow: + yyTok = getToken(); + if (yyTok == Tok_tr || yyTok == Tok_trUtf8) + qWarning("%s:%d: Cannot invoke tr() like this", + qPrintable(yyFileName), yyLineNo); + break; + case Tok_ColonColon: + if (yyBraceDepth == namespaces.count() && yyParenDepth == 0 && !yyTokColonSeen) + functionContext = getFullyQualifiedClassName(allClasses, namespaces, namespaceAliases, prefix, false); + prefix += strColons; + yyTok = getToken(); +#ifdef DIAGNOSE_RETRANSLATABILITY + if (yyTok == Tok_Ident && yyBraceDepth == namespaces.count() && yyParenDepth == 0) + functionName = yyIdent; +#endif + break; + case Tok_RightBrace: + case Tok_Semicolon: + prefix.clear(); + extracomment.clear(); + yyTokColonSeen = false; + if (yyBraceDepth >= 0 && yyBraceDepth + 1 == namespaces.count()) + namespaces.removeLast(); + if (yyBraceDepth == namespaces.count()) { + if (missing_Q_OBJECT) { + if (needs_Q_OBJECT.contains(functionContext)) { + qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro", + qPrintable(yyFileName), yyLineNo, + qPrintable(functionContext)); + } else { + lacks_Q_OBJECT.insert(functionContext); + } + } + functionContext = defaultContext; + missing_Q_OBJECT = false; + } + yyTok = getToken(); + break; + case Tok_Colon: + yyTokColonSeen = true; + yyTok = getToken(); + break; + case Tok_LeftParen: + case Tok_RightParen: + case Tok_LeftBrace: + yyTokColonSeen = false; + yyTok = getToken(); + break; + default: + yyTok = getToken(); + break; + } + } + + if (yyBraceDepth != 0) + qWarning("%s:%d: Unbalanced braces in C++ code (or abuse of the C++" + " preprocessor)\n", + qPrintable(yyFileName), yyBraceLineNo); + else if (yyParenDepth != 0) + qWarning("%s:%d: Unbalanced parentheses in C++ code (or abuse of the C++" + " preprocessor)\n", + qPrintable(yyFileName), yyParenLineNo); +} + +/* + Fetches tr() calls in C++ code in UI files (inside "<function>" + tag). This mechanism is obsolete. +*/ +void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context) +{ + yyInStr = in; + yyInPos = 0; + yyFileName = QString(); + yyCodecIsUtf8 = (translator.codecName() == "UTF-8"); + yyForceUtf8 = true; + yySourceIsUnicode = true; + yySavedBraceDepth.clear(); + yySavedParenDepth.clear(); + yyBraceDepth = 0; + yyParenDepth = 0; + yyCurLineNo = 1; + yyBraceLineNo = 1; + yyParenLineNo = 1; + yyCh = getChar(); + + parse(&translator, context, QString()); +} + + +bool loadCPP(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QString defaultContext = cd.m_defaultContext; + + yyCodecIsUtf8 = (translator.codecName() == "UTF-8"); + yyForceUtf8 = false; + QTextStream ts(&dev); + QByteArray codecName = cd.m_codecForSource.isEmpty() + ? translator.codecName() : cd.m_codecForSource; + ts.setCodec(QTextCodec::codecForName(codecName)); + ts.setAutoDetectUnicode(true); + yySourceCodec = ts.codec(); + if (yySourceCodec->name() == "UTF-16") + translator.setCodecName("System"); + yySourceIsUnicode = yySourceCodec->name().startsWith("UTF-"); + yyInStr = ts.readAll(); + yyInPos = 0; + yyFileName = cd.m_sourceFileName; + yySavedBraceDepth.clear(); + yySavedParenDepth.clear(); + yyBraceDepth = 0; + yyParenDepth = 0; + yyCurLineNo = 1; + yyBraceLineNo = 1; + yyParenLineNo = 1; + yyCh = getChar(); + + parse(&translator, defaultContext, defaultContext); + + return true; +} + +int initCPP() +{ + Translator::FileFormat format; + format.extension = QLatin1String("cpp"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.description = QObject::tr("C++ source files"); + format.loader = &loadCPP; + format.saver = 0; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initCPP) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/formats.pri b/tools/linguist/shared/formats.pri new file mode 100644 index 0000000..9c8072b --- /dev/null +++ b/tools/linguist/shared/formats.pri @@ -0,0 +1,26 @@ + +# infrastructure +QT *= xml + +INCLUDEPATH *= $$PWD + +SOURCES += \ + $$PWD/numerus.cpp \ + $$PWD/translator.cpp \ + $$PWD/translatormessage.cpp + +HEADERS += \ + $$PWD/translator.h \ + $$PWD/translatormessage.h + +# "real" formats readers and writers +SOURCES += \ + $$PWD/qm.cpp \ + $$PWD/qph.cpp \ + $$PWD/po.cpp \ + $$PWD/ts.cpp \ + $$PWD/ui.cpp \ + $$PWD/cpp.cpp \ + $$PWD/java.cpp \ + $$PWD/qscript.cpp \ + $$PWD/xliff.cpp diff --git a/tools/linguist/shared/java.cpp b/tools/linguist/shared/java.cpp new file mode 100644 index 0000000..912a8d7 --- /dev/null +++ b/tools/linguist/shared/java.cpp @@ -0,0 +1,655 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtCore/QRegExp> +#include <QtCore/QStack> +#include <QtCore/QStack> +#include <QtCore/QString> +#include <QtCore/QTextCodec> + +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +enum { Tok_Eof, Tok_class, Tok_return, Tok_tr, + Tok_translate, Tok_Ident, Tok_Package, + Tok_Comment, Tok_String, Tok_Colon, Tok_Dot, + Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen, + Tok_RightParen, Tok_Comma, Tok_Semicolon, + Tok_Integer, Tok_Plus, Tok_PlusPlus, Tok_PlusEq }; + +class Scope +{ + public: + QString name; + enum Type {Clazz, Function, Other} type; + int line; + + Scope(const QString & name, Type type, int line) : + name(name), + type(type), + line(line) + {} + + ~Scope() + {} +}; + +/* + The tokenizer maintains the following global variables. The names + should be self-explanatory. +*/ + +static QString yyFileName; +static QChar yyCh; +static QString yyIdent; +static QString yyComment; +static QString yyString; + + +static qlonglong yyInteger; +static int yyParenDepth; +static int yyLineNo; +static int yyCurLineNo; +static int yyParenLineNo; +static int yyTok; + +// the string to read from and current position in the string +static QString yyInStr; +static int yyInPos; + +// The parser maintains the following global variables. +static QString yyPackage; +static QStack<Scope*> yyScope; +static QString yyDefaultContext; + +static QChar getChar() +{ + if (yyInPos >= yyInStr.size()) + return EOF; + QChar c = yyInStr[yyInPos++]; + if (c.unicode() == '\n') + ++yyCurLineNo; + return c.unicode(); +} + +static int getToken() +{ + const char tab[] = "bfnrt\"\'\\"; + const char backTab[] = "\b\f\n\r\t\"\'\\"; + + yyIdent.clear(); + yyComment.clear(); + yyString.clear(); + + while ( yyCh != EOF ) { + yyLineNo = yyCurLineNo; + + if ( yyCh.isLetter() || yyCh.toLatin1() == '_' ) { + do { + yyIdent.append(yyCh); + yyCh = getChar(); + } while ( yyCh.isLetterOrNumber() || yyCh.toLatin1() == '_' ); + + if (yyTok != Tok_Dot) { + switch ( yyIdent.at(0).toLatin1() ) { + case 'r': + if ( yyIdent == QLatin1String("return") ) + return Tok_return; + break; + case 'c': + if ( yyIdent == QLatin1String("class") ) + return Tok_class; + break; + } + } + switch ( yyIdent.at(0).toLatin1() ) { + case 'T': + // TR() for when all else fails + if ( yyIdent == QLatin1String("TR") ) + return Tok_tr; + break; + case 'p': + if( yyIdent == QLatin1String("package") ) + return Tok_Package; + break; + case 't': + if ( yyIdent == QLatin1String("tr") ) + return Tok_tr; + if ( yyIdent == QLatin1String("translate") ) + return Tok_translate; + } + return Tok_Ident; + } else { + switch ( yyCh.toLatin1() ) { + + case '/': + yyCh = getChar(); + if ( yyCh == QLatin1Char('/') ) { + do { + yyCh = getChar(); + if (yyCh == EOF) + break; + yyComment.append(yyCh); + } while (yyCh != QLatin1Char('\n')); + return Tok_Comment; + + } else if ( yyCh == QLatin1Char('*') ) { + bool metAster = false; + bool metAsterSlash = false; + + while ( !metAsterSlash ) { + yyCh = getChar(); + if ( yyCh == EOF ) { + qFatal( "%s: Unterminated Java comment starting at" + " line %d\n", + qPrintable(yyFileName), yyLineNo ); + + return Tok_Comment; + } + + yyComment.append( yyCh ); + + if ( yyCh == QLatin1Char('*') ) + metAster = true; + else if ( metAster && yyCh == QLatin1Char('/') ) + metAsterSlash = true; + else + metAster = false; + } + yyComment.chop(2); + yyCh = getChar(); + + return Tok_Comment; + } + break; + case '"': + yyCh = getChar(); + + while ( yyCh != EOF && yyCh != QLatin1Char('\n') && yyCh != QLatin1Char('"') ) { + if ( yyCh == QLatin1Char('\\') ) { + yyCh = getChar(); + if ( yyCh == QLatin1Char('u') ) { + yyCh = getChar(); + uint unicode(0); + for (int i = 4; i > 0; --i) { + unicode = unicode << 4; + if( yyCh.isDigit() ) { + unicode += yyCh.digitValue(); + } + else { + int sub(yyCh.toLower().toAscii() - 87); + if( sub > 15 || sub < 10) { + qFatal( "%s:%d: Invalid Unicode", + qPrintable(yyFileName), yyLineNo ); + } + unicode += sub; + } + yyCh = getChar(); + } + yyString.append(QChar(unicode)); + } + else if ( yyCh == QLatin1Char('\n') ) { + yyCh = getChar(); + } + else { + yyString.append( QLatin1Char(backTab[strchr( tab, yyCh.toAscii() ) - tab]) ); + yyCh = getChar(); + } + } else { + yyString.append(yyCh); + yyCh = getChar(); + } + } + + if ( yyCh != QLatin1Char('"') ) + qFatal( "%s:%d: Unterminated string", + qPrintable(yyFileName), yyLineNo ); + + yyCh = getChar(); + + return Tok_String; + + case ':': + yyCh = getChar(); + return Tok_Colon; + case '\'': + yyCh = getChar(); + + if ( yyCh == QLatin1Char('\\') ) + yyCh = getChar(); + do { + yyCh = getChar(); + } while ( yyCh != EOF && yyCh != QLatin1Char('\'') ); + yyCh = getChar(); + break; + case '{': + yyCh = getChar(); + return Tok_LeftBrace; + case '}': + yyCh = getChar(); + return Tok_RightBrace; + case '(': + if (yyParenDepth == 0) + yyParenLineNo = yyCurLineNo; + yyParenDepth++; + yyCh = getChar(); + return Tok_LeftParen; + case ')': + if (yyParenDepth == 0) + yyParenLineNo = yyCurLineNo; + yyParenDepth--; + yyCh = getChar(); + return Tok_RightParen; + case ',': + yyCh = getChar(); + return Tok_Comma; + case '.': + yyCh = getChar(); + return Tok_Dot; + case ';': + yyCh = getChar(); + return Tok_Semicolon; + case '+': + yyCh = getChar(); + if (yyCh == QLatin1Char('+')) { + yyCh = getChar(); + return Tok_PlusPlus; + } + if( yyCh == QLatin1Char('=') ){ + yyCh = getChar(); + return Tok_PlusEq; + } + return Tok_Plus; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + QByteArray ba; + ba += yyCh.toLatin1(); + yyCh = getChar(); + bool hex = yyCh == QLatin1Char('x'); + if ( hex ) { + ba += yyCh.toLatin1(); + yyCh = getChar(); + } + while ( hex ? isxdigit(yyCh.toLatin1()) : yyCh.isDigit() ) { + ba += yyCh.toLatin1(); + yyCh = getChar(); + } + bool ok; + yyInteger = ba.toLongLong(&ok); + if (ok) return Tok_Integer; + break; + } + default: + yyCh = getChar(); + } + } + } + return Tok_Eof; +} + +static bool match( int t ) +{ + bool matches = ( yyTok == t ); + if ( matches ) + yyTok = getToken(); + return matches; +} + +static bool matchString( QString &s ) +{ + if ( yyTok != Tok_String ) + return false; + + s = yyString; + yyTok = getToken(); + while ( yyTok == Tok_Plus ) { + yyTok = getToken(); + if (yyTok == Tok_String) + s += yyString; + else { + qWarning( "%s:%d: String used in translation can only contain strings" + " concatenated with other strings, not expressions or numbers.", + qPrintable(yyFileName), yyLineNo ); + return false; + } + yyTok = getToken(); + } + return true; +} + +static bool matchInteger( qlonglong *number) +{ + bool matches = (yyTok == Tok_Integer); + if (matches) { + yyTok = getToken(); + *number = yyInteger; + } + return matches; +} + +static bool matchStringOrNull(QString &s) +{ + bool matches = matchString(s); + qlonglong num = 0; + if (!matches) matches = matchInteger(&num); + return matches && num == 0; +} + +/* + * match any expression that can return a number, which can be + * 1. Literal number (e.g. '11') + * 2. simple identifier (e.g. 'm_count') + * 3. simple function call (e.g. 'size()' ) + * 4. function call on an object (e.g. 'list.size()') + * 5. function call on an object (e.g. 'list->size()') + * + * Other cases: + * size(2,4) + * list().size() + * list(a,b).size(2,4) + * etc... + */ +static bool matchExpression() +{ + if (match(Tok_Integer)) { + return true; + } + + int parenlevel = 0; + while (match(Tok_Ident) || parenlevel > 0) { + if (yyTok == Tok_RightParen) { + if (parenlevel == 0) break; + --parenlevel; + yyTok = getToken(); + } else if (yyTok == Tok_LeftParen) { + yyTok = getToken(); + if (yyTok == Tok_RightParen) { + yyTok = getToken(); + } else { + ++parenlevel; + } + } else if (yyTok == Tok_Ident) { + continue; + } else if (parenlevel == 0) { + return false; + } + } + return true; +} + +static const QString context() +{ + QString context(yyPackage); + bool innerClass = false; + for (int i = 0; i < yyScope.size(); ++i) { + if (yyScope.at(i)->type == Scope::Clazz) { + if (innerClass) + context.append(QLatin1String("$")); + else + context.append(QLatin1String(".")); + + context.append(yyScope.at(i)->name); + innerClass = true; + } + } + return context.isEmpty() ? yyDefaultContext : context; +} + +static void recordMessage( + Translator *tor, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, bool plural) +{ + TranslatorMessage msg( + context, text, comment, QString(), + yyFileName, yyLineNo, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + tor->extend(msg); +} + +static void parse( Translator *tor ) +{ + QString text; + QString com; + QString extracomment; + + yyCh = getChar(); + + yyTok = getToken(); + while ( yyTok != Tok_Eof ) { + switch ( yyTok ) { + case Tok_class: + yyTok = getToken(); + if(yyTok == Tok_Ident) { + yyScope.push(new Scope(yyIdent, Scope::Clazz, yyLineNo)); + } + else { + qFatal( "%s:%d: Class must be followed by a classname", + qPrintable(yyFileName), yyLineNo ); + } + while (!match(Tok_LeftBrace)) { + yyTok = getToken(); + } + break; + + case Tok_tr: + yyTok = getToken(); + if ( match(Tok_LeftParen) && matchString(text) ) { + com.clear(); + bool plural = false; + + if ( match(Tok_RightParen) ) { + // no comment + } else if (match(Tok_Comma) && matchStringOrNull(com)) { //comment + if ( match(Tok_RightParen)) { + // ok, + } else if (match(Tok_Comma)) { + plural = true; + } + } + if (!text.isEmpty()) + recordMessage(tor, context(), text, com, extracomment, plural); + } + break; + case Tok_translate: + { + QString contextOverride; + yyTok = getToken(); + if ( match(Tok_LeftParen) && + matchString(contextOverride) && + match(Tok_Comma) && + matchString(text) ) { + + com.clear(); + bool plural = false; + if (!match(Tok_RightParen)) { + // look for comment + if ( match(Tok_Comma) && matchStringOrNull(com)) { + if (!match(Tok_RightParen)) { + if (match(Tok_Comma) && matchExpression() && match(Tok_RightParen)) { + plural = true; + } else { + break; + } + } + } else { + break; + } + } + if (!text.isEmpty()) + recordMessage(tor, contextOverride, text, com, extracomment, plural); + } + } + break; + + case Tok_Ident: + yyTok = getToken(); + break; + + case Tok_Comment: + if (yyComment.startsWith(QLatin1Char(':'))) { + yyComment.remove(0, 1); + extracomment.append(yyComment); + } + yyTok = getToken(); + break; + + case Tok_RightBrace: + if ( yyScope.isEmpty() ) { + qFatal( "%s:%d: Unbalanced right brace in Java code\n", + qPrintable(yyFileName), yyLineNo ); + } + else + delete (yyScope.pop()); + extracomment.clear(); + yyTok = getToken(); + break; + + case Tok_LeftBrace: + yyScope.push(new Scope(QString(), Scope::Other, yyLineNo)); + yyTok = getToken(); + break; + + case Tok_Semicolon: + extracomment.clear(); + yyTok = getToken(); + break; + + case Tok_Package: + yyTok = getToken(); + while(!match(Tok_Semicolon)) { + switch(yyTok) { + case Tok_Ident: + yyPackage.append(yyIdent); + break; + case Tok_Dot: + yyPackage.append(QLatin1String(".")); + break; + default: + qFatal( "%s:%d: Package keyword should be followed by com.package.name;", + qPrintable(yyFileName), yyLineNo ); + break; + } + yyTok = getToken(); + } + break; + + default: + yyTok = getToken(); + } + } + + if ( !yyScope.isEmpty() ) + qFatal( "%s:%d: Unbalanced braces in Java code\n", + qPrintable(yyFileName), yyScope.top()->line ); + else if ( yyParenDepth != 0 ) + qFatal( "%s:%d: Unbalanced parentheses in Java code\n", + qPrintable(yyFileName), yyParenLineNo ); +} + + +bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + //void LupdateApplication::fetchtr_java( const QString &fileName, Translator *tor, + //const QString &defaultContext, bool mustExist, const QByteArray &codecForSource ) + + yyDefaultContext = cd.m_defaultContext; + yyInPos = -1; + yyFileName = cd.m_sourceFileName; + yyPackage.clear(); + yyScope.clear(); + yyTok = -1; + yyParenDepth = 0; + yyCurLineNo = 0; + yyParenLineNo = 1; + + QTextStream ts(&dev); + QByteArray codecName; + if (!cd.m_codecForSource.isEmpty()) + codecName = cd.m_codecForSource; + else + codecName = translator.codecName(); // Just because it should be latin1 already + ts.setCodec(QTextCodec::codecForName(codecName)); + ts.setAutoDetectUnicode(true); + yyInStr = ts.readAll(); + yyInPos = 0; + yyFileName = cd.m_sourceFileName; + yyCurLineNo = 1; + yyParenLineNo = 1; + yyCh = getChar(); + + parse(&translator); + + // Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it. + translator.setCodecName("UTF-8"); + return true; +} + +int initJava() +{ + Translator::FileFormat format; + format.extension = QLatin1String("java"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.description = QObject::tr("Java source files"); + format.loader = &loadJava; + format.saver = 0; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initJava) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/make-qscript.sh b/tools/linguist/shared/make-qscript.sh new file mode 100755 index 0000000..42cab7a --- /dev/null +++ b/tools/linguist/shared/make-qscript.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +me=$(dirname $0) +mkdir -p $me/out +(cd $me/out && ${QLALR-qlalr} --no-debug --troll --no-lines ../qscript.g) + +for f in $me/out/*.{h,cpp}; do + n=$(basename $f) + p4 open $me/../$n + cp $f $me/../$n +done + +p4 revert -a $me/../... +p4 diff -du $me/../... diff --git a/tools/linguist/shared/numerus.cpp b/tools/linguist/shared/numerus.cpp new file mode 100644 index 0000000..f3a29cc --- /dev/null +++ b/tools/linguist/shared/numerus.cpp @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QMap> + +#include <private/qtranslator_p.h> + +QT_BEGIN_NAMESPACE + +static const uchar englishStyleRules[] = + { Q_EQ, 1 }; +static const uchar frenchStyleRules[] = + { Q_LEQ, 1 }; +static const uchar latvianRules[] = + { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, + Q_NEQ, 0 }; +static const uchar irishStyleRules[] = + { Q_EQ, 1, Q_NEWRULE, + Q_EQ, 2 }; +static const uchar czechRules[] = + { Q_MOD_100 | Q_EQ, 1, Q_NEWRULE, + Q_MOD_100 | Q_BETWEEN, 2, 4 }; +static const uchar slovakRules[] = + { Q_EQ, 1, Q_NEWRULE, + Q_BETWEEN, 2, 4 }; +static const uchar macedonianRules[] = + { Q_MOD_10 | Q_EQ, 1, Q_NEWRULE, + Q_MOD_10 | Q_EQ, 2 }; +static const uchar lithuanianRules[] = + { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, + Q_MOD_10 | Q_EQ, 2, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; +static const uchar russianStyleRules[] = + { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, + Q_MOD_10 | Q_BETWEEN, 2, 4, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; +static const uchar polishRules[] = + { Q_EQ, 1, Q_NEWRULE, + Q_MOD_10 | Q_BETWEEN, 2, 4, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; +static const uchar romanianRules[] = + { Q_EQ, 1, Q_NEWRULE, + Q_EQ, 0, Q_OR, Q_MOD_100 | Q_BETWEEN, 1, 19 }; +static const uchar slovenianRules[] = + { Q_MOD_100 | Q_EQ, 1, Q_NEWRULE, + Q_MOD_100 | Q_EQ, 2, Q_NEWRULE, + Q_MOD_100 | Q_BETWEEN, 3, 4 }; +static const uchar malteseRules[] = + { Q_EQ, 1, Q_NEWRULE, + Q_EQ, 0, Q_OR, Q_MOD_100 | Q_BETWEEN, 1, 10, Q_NEWRULE, + Q_MOD_100 | Q_BETWEEN, 11, 19 }; +static const uchar welshRules[] = + { Q_EQ, 0, Q_NEWRULE, + Q_EQ, 1, Q_NEWRULE, + Q_BETWEEN, 2, 5, Q_NEWRULE, + Q_EQ, 6 }; +static const uchar arabicRules[] = + { Q_EQ, 0, Q_NEWRULE, + Q_EQ, 1, Q_NEWRULE, + Q_EQ, 2, Q_NEWRULE, + Q_MOD_100 | Q_BETWEEN, 3, 10, Q_NEWRULE, + Q_MOD_100 | Q_NEQ, 0 }; + +static const char * const japaneseStyleForms[] = { "Universal Form", 0 }; +static const char * const englishStyleForms[] = { "Singular", "Plural", 0 }; +static const char * const frenchStyleForms[] = { "Singular", "Plural", 0 }; +static const char * const latvianForms[] = { "Singular", "Plural", "Nullar", 0 }; +static const char * const irishStyleForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const czechForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const slovakForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const macedonianForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const lithuanianForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const russianStyleForms[] = { "Singular", "Dual", "Plural", 0 }; +static const char * const polishForms[] = { "Singular", "Paucal", "Plural", 0 }; +static const char * const romanianForms[] = + { "Singular", "Plural Form for 2 to 19", "Plural", 0 }; +static const char * const slovenianForms[] = { "Singular", "Dual", "Trial", "Plural", 0 }; +static const char * const malteseForms[] = + { "Singular", "Plural Form for 2 to 10", "Plural Form for 11 to 19", "Plural", 0 }; +static const char * const welshForms[] = + { "Nullar", "Singular", "Dual", "Sexal", "Plural", 0 }; +static const char * const arabicForms[] = + { "Nullar", "Singular", "Dual", "Minority Plural", "Plural", "Plural Form for 100, 200, ...", 0 }; + +#define EOL QLocale::C + +static const QLocale::Language japaneseStyleLanguages[] = { + QLocale::Afan, + QLocale::Armenian, + QLocale::Bhutani, + QLocale::Bislama, + QLocale::Burmese, + QLocale::Chinese, + QLocale::FijiLanguage, + QLocale::Guarani, + QLocale::Hungarian, + QLocale::Indonesian, + QLocale::Japanese, + QLocale::Javanese, + QLocale::Korean, + QLocale::Malay, + QLocale::NauruLanguage, + QLocale::Persian, + QLocale::Sundanese, + QLocale::Thai, + QLocale::Tibetan, + QLocale::Vietnamese, + QLocale::Yoruba, + QLocale::Zhuang, + EOL +}; + +static const QLocale::Language englishStyleLanguages[] = { + QLocale::Abkhazian, + QLocale::Afar, + QLocale::Afrikaans, + QLocale::Albanian, + QLocale::Amharic, + QLocale::Assamese, + QLocale::Aymara, + QLocale::Azerbaijani, + QLocale::Bashkir, + QLocale::Basque, + QLocale::Bengali, + QLocale::Bihari, + // Missing: Bokmal, + QLocale::Bulgarian, + QLocale::Cambodian, + QLocale::Catalan, + QLocale::Cornish, + QLocale::Corsican, + QLocale::Danish, + QLocale::Dutch, + QLocale::English, + QLocale::Esperanto, + QLocale::Estonian, + QLocale::Faroese, + QLocale::Finnish, + // Missing: Friulian, + QLocale::Frisian, + QLocale::Galician, + QLocale::Georgian, + QLocale::German, + QLocale::Greek, + QLocale::Greenlandic, + QLocale::Gujarati, + QLocale::Hausa, + QLocale::Hebrew, + QLocale::Hindi, + QLocale::Icelandic, + QLocale::Interlingua, + QLocale::Interlingue, + QLocale::Italian, + QLocale::Kannada, + QLocale::Kashmiri, + QLocale::Kazakh, + QLocale::Kinyarwanda, + QLocale::Kirghiz, + QLocale::Kurdish, + QLocale::Kurundi, + QLocale::Laothian, + QLocale::Latin, + // Missing: Letzeburgesch, + QLocale::Lingala, + QLocale::Malagasy, + QLocale::Malayalam, + QLocale::Marathi, + QLocale::Mongolian, + // Missing: Nahuatl, + QLocale::Nepali, + // Missing: Northern Sotho, + QLocale::Norwegian, + QLocale::Nynorsk, + QLocale::Occitan, + QLocale::Oriya, + QLocale::Pashto, + QLocale::Portuguese, + QLocale::Punjabi, + QLocale::Quechua, + QLocale::RhaetoRomance, + QLocale::Sesotho, + QLocale::Setswana, + QLocale::Shona, + QLocale::Sindhi, + QLocale::Singhalese, + QLocale::Siswati, + QLocale::Somali, + QLocale::Spanish, + QLocale::Swahili, + QLocale::Swedish, + QLocale::Tagalog, + QLocale::Tajik, + QLocale::Tamil, + QLocale::Tatar, + QLocale::Telugu, + QLocale::TongaLanguage, + QLocale::Tsonga, + QLocale::Turkish, + QLocale::Turkmen, + QLocale::Twi, + QLocale::Uigur, + QLocale::Uzbek, + QLocale::Volapuk, + QLocale::Wolof, + QLocale::Xhosa, + QLocale::Yiddish, + QLocale::Zulu, + EOL +}; +static const QLocale::Language frenchStyleLanguages[] = { + // keep synchronized with frenchStyleCountries + QLocale::Breton, + QLocale::French, + QLocale::Portuguese, + // Missing: Filipino, + QLocale::Tigrinya, + // Missing: Walloon + EOL +}; +static const QLocale::Language latvianLanguage[] = { QLocale::Latvian, EOL }; +static const QLocale::Language irishStyleLanguages[] = { + QLocale::Divehi, + QLocale::Gaelic, + QLocale::Inuktitut, + QLocale::Inupiak, + QLocale::Irish, + QLocale::Manx, + QLocale::Maori, + // Missing: Sami, + QLocale::Samoan, + QLocale::Sanskrit, + EOL +}; +static const QLocale::Language czechLanguage[] = { QLocale::Czech, EOL }; +static const QLocale::Language slovakLanguage[] = { QLocale::Slovak, EOL }; +static const QLocale::Language macedonianLanguage[] = { QLocale::Macedonian, EOL }; +static const QLocale::Language lithuanianLanguage[] = { QLocale::Lithuanian, EOL }; +static const QLocale::Language russianStyleLanguages[] = { + QLocale::Bosnian, + QLocale::Byelorussian, + QLocale::Croatian, + QLocale::Russian, + QLocale::Serbian, + QLocale::SerboCroatian, + QLocale::Ukrainian, + EOL +}; +static const QLocale::Language polishLanguage[] = { QLocale::Polish, EOL }; +static const QLocale::Language romanianLanguages[] = { + QLocale::Moldavian, + QLocale::Romanian, + EOL +}; +static const QLocale::Language slovenianLanguage[] = { QLocale::Slovenian, EOL }; +static const QLocale::Language malteseLanguage[] = { QLocale::Maltese, EOL }; +static const QLocale::Language welshLanguage[] = { QLocale::Welsh, EOL }; +static const QLocale::Language arabicLanguage[] = { QLocale::Arabic, EOL }; + +static const QLocale::Country frenchStyleCountries[] = { + // keep synchronized with frenchStyleLanguages + QLocale::AnyCountry, + QLocale::AnyCountry, + QLocale::Brazil, + QLocale::AnyCountry +}; +struct NumerusTableEntry { + const uchar *rules; + int rulesSize; + const char * const *forms; + const QLocale::Language *languages; + const QLocale::Country *countries; +}; + +static const NumerusTableEntry numerusTable[] = { + { 0, 0, japaneseStyleForms, japaneseStyleLanguages, 0 }, + { englishStyleRules, sizeof(englishStyleRules), englishStyleForms, englishStyleLanguages, 0 }, + { frenchStyleRules, sizeof(frenchStyleRules), frenchStyleForms, frenchStyleLanguages, + frenchStyleCountries }, + { latvianRules, sizeof(latvianRules), latvianForms, latvianLanguage, 0 }, + { irishStyleRules, sizeof(irishStyleRules), irishStyleForms, irishStyleLanguages, 0 }, + { czechRules, sizeof(czechRules), czechForms, czechLanguage, 0 }, + { slovakRules, sizeof(slovakRules), slovakForms, slovakLanguage, 0 }, + { macedonianRules, sizeof(macedonianRules), macedonianForms, macedonianLanguage, 0 }, + { lithuanianRules, sizeof(lithuanianRules), lithuanianForms, lithuanianLanguage, 0 }, + { russianStyleRules, sizeof(russianStyleRules), russianStyleForms, russianStyleLanguages, 0 }, + { polishRules, sizeof(polishRules), polishForms, polishLanguage, 0 }, + { romanianRules, sizeof(romanianRules), romanianForms, romanianLanguages, 0 }, + { slovenianRules, sizeof(slovenianRules), slovenianForms, slovenianLanguage, 0 }, + { malteseRules, sizeof(malteseRules), malteseForms, malteseLanguage, 0 }, + { welshRules, sizeof(welshRules), welshForms, welshLanguage, 0 }, + { arabicRules, sizeof(arabicRules), arabicForms, arabicLanguage, 0 } +}; + +static const int NumerusTableSize = sizeof(numerusTable) / sizeof(numerusTable[0]); + +// magic number for the file +static const int MagicLength = 16; +static const uchar magic[MagicLength] = { + 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95, + 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd +}; + +bool getNumerusInfo(QLocale::Language language, QLocale::Country country, + QByteArray *rules, QStringList *forms) +{ + while (true) { + for (int i = 0; i < NumerusTableSize; ++i) { + const NumerusTableEntry &entry = numerusTable[i]; + for (int j = 0; entry.languages[j] != EOL; ++j) { + if (entry.languages[j] == language + && ((!entry.countries && country == QLocale::AnyCountry) + || (entry.countries && entry.countries[j] == country))) { + if (rules) { + *rules = QByteArray::fromRawData(reinterpret_cast<const char *>(entry.rules), + entry.rulesSize); + } + if (forms) { + forms->clear(); + for (int k = 0; entry.forms[k]; ++k) + forms->append(QLatin1String(entry.forms[k])); + } + return true; + } + } + } + + if (country == QLocale::AnyCountry) + break; + country = QLocale::AnyCountry; + } + return false; +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/po.cpp b/tools/linguist/shared/po.cpp new file mode 100644 index 0000000..e9375e9 --- /dev/null +++ b/tools/linguist/shared/po.cpp @@ -0,0 +1,662 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QIODevice> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QTextStream> + +#include <ctype.h> + +#define MAGIC_OBSOLETE_REFERENCE "Obsolete_PO_entries" + +// Uncomment if you wish to hard wrap long lines in .po files. Note that this +// affects only msg strings, not comments. +//#define HARD_WRAP_LONG_WORDS + +QT_BEGIN_NAMESPACE + +static const int MAX_LEN = 79; + +static QString poEscapedString(const QString &prefix, const QString &keyword, + bool noWrap, const QString &ba) +{ + QStringList lines; + int off = 0; + QString res; + while (off < ba.length()) { + ushort c = ba[off++].unicode(); + switch (c) { + case '\n': + res += QLatin1String("\\n"); + lines.append(res); + res.clear(); + break; + case '\r': + res += QLatin1String("\\r"); + break; + case '\t': + res += QLatin1String("\\t"); + break; + case '\v': + res += QLatin1String("\\v"); + break; + case '\a': + res += QLatin1String("\\a"); + break; + case '\b': + res += QLatin1String("\\b"); + break; + case '\f': + res += QLatin1String("\\f"); + break; + case '"': + res += QLatin1String("\\\""); + break; + case '\\': + res += QLatin1String("\\\\"); + break; + default: + if (c < 32) { + res += QLatin1String("\\x"); + res += QString::number(c, 16); + if (off < ba.length() && isxdigit(ba[off].unicode())) + res += QLatin1String("\"\""); + } else { + res += QChar(c); + } + break; + } + } + if (!res.isEmpty()) + lines.append(res); + if (!lines.isEmpty()) { + if (!noWrap) { + if (lines.count() != 1 || + lines.first().length() > MAX_LEN - keyword.length() - prefix.length() - 3) + { + QStringList olines = lines; + lines = QStringList(QString()); + const int maxlen = MAX_LEN - prefix.length() - 2; + foreach (const QString &line, olines) { + int off = 0; + while (off + maxlen < line.length()) { + int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1) + 1; + if (idx == off) { +#ifdef HARD_WRAP_LONG_WORDS + // This doesn't seem too nice, but who knows ... + idx = off + maxlen; +#else + idx = line.indexOf(QLatin1Char(' '), off + maxlen) + 1; + if (!idx) + break; +#endif + } + lines.append(line.mid(off, idx - off)); + off = idx; + } + lines.append(line.mid(off)); + } + } + } else if (lines.count() > 1) { + lines.prepend(QString()); + } + } + return prefix + keyword + QLatin1String(" \"") + + lines.join(QLatin1String("\"\n") + prefix + QLatin1Char('"')) + + QLatin1String("\"\n"); +} + +static QString poEscapedLines(const QString &prefix, bool addSpace, const QStringList &lines) +{ + QString out; + foreach (const QString &line, lines) { + out += prefix; + if (addSpace && !line.isEmpty()) + out += QLatin1Char(' ' ); + out += line; + out += QLatin1Char('\n'); + } + return out; +} + +static QString poEscapedLines(const QString &prefix, bool addSpace, const QString &in0) +{ + QString in = in0; + if (in.endsWith(QLatin1Char('\n'))) + in.chop(1); + return poEscapedLines(prefix, addSpace, in.split(QLatin1Char('\n'))); +} + +static QString poWrappedEscapedLines(const QString &prefix, bool addSpace, const QString &line) +{ + const int maxlen = MAX_LEN - prefix.length(); + QStringList lines; + int off = 0; + while (off + maxlen < line.length()) { + int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1); + if (idx < off) { +#if 0 //def HARD_WRAP_LONG_WORDS + // This cannot work without messing up semantics, so do not even try. +#else + idx = line.indexOf(QLatin1Char(' '), off + maxlen); + if (idx < 0) + break; +#endif + } + lines.append(line.mid(off, idx - off)); + off = idx + 1; + } + lines.append(line.mid(off)); + return poEscapedLines(prefix, addSpace, lines); +} + +struct PoItem +{ +public: + PoItem() + : isPlural(false), isFuzzy(false) + {} + + +public: + QString id; + QString context; + QString tscomment; + QString oldTscomment; + QString lineNumber; + QString fileName; + QString references; + QString translatorComments; + QString automaticComments; + QString msgId; + QString oldMsgId; + QStringList msgStr; + bool isPlural; + bool isFuzzy; + QHash<QString, QString> extra; +}; + + +static bool isTranslationLine(const QString &line) +{ + return line.startsWith(QLatin1String("#~ msgstr")) + || line.startsWith(QLatin1String("msgstr")); +} + +static QString slurpEscapedString(const QStringList &lines, int & l, + int offset, const QString &prefix, ConversionData &cd) +{ + QString msg; + int stoff; + + for (; l < lines.size(); ++l) { + const QString &line = lines.at(l); + if (line.isEmpty() || !line.startsWith(prefix)) + break; + while (line[offset].isSpace()) // No length check, as string has no trailing spaces. + offset++; + if (line[offset].unicode() != '"') + break; + offset++; + forever { + if (offset == line.length()) + goto premature_eol; + ushort c = line[offset++].unicode(); + if (c == '"') { + if (offset == line.length()) + break; + while (line[offset].isSpace()) + offset++; + if (line[offset++].unicode() != '"') { + cd.appendError(QString::fromLatin1( + "PO parsing error: extra characters on line %1.") + .arg(l + 1)); + break; + } + continue; + } + if (c == '\\') { + if (offset == line.length()) + goto premature_eol; + c = line[offset++].unicode(); + switch (c) { + case 'r': + msg += QLatin1Char('\r'); // Maybe just throw it away? + break; + case 'n': + msg += QLatin1Char('\n'); + break; + case 't': + msg += QLatin1Char('\t'); + break; + case 'v': + msg += QLatin1Char('\v'); + break; + case 'a': + msg += QLatin1Char('\a'); + break; + case 'b': + msg += QLatin1Char('\b'); + break; + case 'f': + msg += QLatin1Char('\f'); + break; + case '"': + msg += QLatin1Char('"'); + break; + case '\\': + msg += QLatin1Char('\\'); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + stoff = offset - 1; + while ((c = line[offset].unicode()) >= '0' && c <= '7') + if (++offset == line.length()) + goto premature_eol; + msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 8)); + break; + case 'x': + stoff = offset; + while (isxdigit(line[offset].unicode())) + if (++offset == line.length()) + goto premature_eol; + msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 16)); + break; + default: + cd.appendError(QString::fromLatin1( + "PO parsing error: invalid escape '\\%1' (line %2).") + .arg(QChar(c)).arg(l + 1)); + msg += QLatin1Char('\\'); + msg += QChar(c); + break; + } + } else { + msg += QChar(c); + } + } + offset = prefix.size(); + } + --l; + return msg; + +premature_eol: + cd.appendError(QString::fromLatin1( + "PO parsing error: premature end of line %1.").arg(l + 1)); + return QString(); +} + +static void slurpComment(QString &msg, const QStringList &lines, int & l) +{ + const QChar newline = QLatin1Char('\n'); + QString prefix = lines.at(l); + for (int i = 1; ; i++) { + if (prefix.at(i).unicode() != ' ') { + prefix.truncate(i); + break; + } + } + for (; l < lines.size(); ++l) { + const QString &line = lines.at(l); + if (line.startsWith(prefix)) + msg += line.mid(prefix.size()); + else if (line != QLatin1String("#")) + break; + msg += newline; + } + --l; +} + +bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + const QChar quote = QLatin1Char('"'); + const QChar newline = QLatin1Char('\n'); + QTextStream in(&dev); + bool error = false; + + // format of a .po file entry: + // white-space + // # translator-comments + // #. automatic-comments + // #: reference... + // #, flag... + // #~ msgctxt, msgid*, msgstr - used for obsoleted messages + // #| msgctxt, msgid* previous untranslated-string - for fuzzy message + // msgctx string-context + // msgid untranslated-string + // -- For singular: + // msgstr translated-string + // -- For plural: + // msgid_plural untranslated-string-plural + // msgstr[0] translated-string + // ... + + // we need line based lookahead below. + QStringList lines; + while (!in.atEnd()) + lines.append(in.readLine().trimmed()); + lines.append(QString()); + + int l = 0; + PoItem item; + for (; l != lines.size(); ++l) { + QString line = lines.at(l); + if (line.isEmpty()) + continue; + if (isTranslationLine(line)) { + bool isObsolete = line.startsWith(QLatin1String("#~ msgstr")); + const QString prefix = QLatin1String(isObsolete ? "#~ " : ""); + while (true) { + int idx = line.indexOf(QLatin1Char(' '), prefix.length()); + item.msgStr.append(slurpEscapedString(lines, l, idx, prefix, cd)); + if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1))) + break; + ++l; + line = lines.at(l); + } + if (item.msgId.isEmpty()) { + QRegExp rx(QLatin1String("\\bX-Language: ([^\n]*)\n")); + int idx = rx.indexIn(item.msgStr.first()); + if (idx >= 0) { + translator.setLanguageCode(rx.cap(1)); + item.msgStr.first().remove(idx, rx.matchedLength()); + } + QRegExp rx2(QLatin1String("\\bX-Source-Language: ([^\n]*)\n")); + int idx2 = rx2.indexIn(item.msgStr.first()); + if (idx2 >= 0) { + translator.setSourceLanguageCode(rx2.cap(1)); + item.msgStr.first().remove(idx2, rx2.matchedLength()); + } + if (item.msgStr.first().indexOf( + QRegExp(QLatin1String("\\bX-Virgin-Header:[^\n]*\n"))) >= 0) { + item = PoItem(); + continue; + } + } + // build translator message + TranslatorMessage msg; + msg.setContext(item.context); + if (!item.references.isEmpty()) { + foreach (const QString &ref, + item.references.split(QRegExp(QLatin1String("\\s")), + QString::SkipEmptyParts)) { + int pos = ref.lastIndexOf(QLatin1Char(':')); + if (pos != -1) + msg.addReference(ref.left(pos), ref.mid(pos + 1).toInt()); + } + } else if (isObsolete) { + msg.setFileName(QLatin1String(MAGIC_OBSOLETE_REFERENCE)); + } + msg.setId(item.id); + msg.setSourceText(item.msgId); + msg.setOldSourceText(item.oldMsgId); + msg.setComment(item.tscomment); + msg.setOldComment(item.oldTscomment); + msg.setExtraComment(item.automaticComments); + msg.setTranslatorComment(item.translatorComments); + msg.setPlural(item.isPlural || item.msgStr.size() > 1); + msg.setTranslations(item.msgStr); + if (isObsolete) + msg.setType(TranslatorMessage::Obsolete); + else if (item.isFuzzy) + msg.setType(TranslatorMessage::Unfinished); + else + msg.setType(TranslatorMessage::Finished); + msg.setExtras(item.extra); + + //qDebug() << "WRITE: " << context; + //qDebug() << "SOURCE: " << msg.sourceText(); + //qDebug() << flags << msg.m_extra; + translator.append(msg); + item = PoItem(); + } else if (line.startsWith(QLatin1Char('#'))) { + switch(line.size() < 2 ? 0 : line.at(1).unicode()) { + case ':': + item.references += line.mid(3); + item.references += newline; + break; + case ',': { + QStringList flags = + line.mid(2).split(QRegExp(QLatin1String("[, ]")), + QString::SkipEmptyParts); + if (flags.removeOne(QLatin1String("fuzzy"))) + item.isFuzzy = true; + TranslatorMessage::ExtraData::const_iterator it = + item.extra.find(QLatin1String("po-flags")); + if (it != item.extra.end()) + flags.prepend(*it); + if (!flags.isEmpty()) + item.extra[QLatin1String("po-flags")] = flags.join(QLatin1String(", ")); + break; + } + case 0: + item.translatorComments += newline; + break; + case ' ': + slurpComment(item.translatorComments, lines, l); + break; + case '.': + if (line.startsWith(QLatin1String("#. ts-context "))) { + item.context = line.mid(14); + } else if (line.startsWith(QLatin1String("#. ts-id "))) { + item.id = line.mid(9); + } else { + item.automaticComments += line.mid(3); + item.automaticComments += newline; + } + break; + case '|': + if (line.startsWith(QLatin1String("#| msgid "))) { + item.oldMsgId = slurpEscapedString(lines, l, 9, QLatin1String("#| "), cd); + } else if (line.startsWith(QLatin1String("#| msgid_plural "))) { + QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#| "), cd); + if (extra != item.oldMsgId) + item.extra[QLatin1String("po-old_msgid_plural")] = extra; + } else if (line.startsWith(QLatin1String("#| msgctxt "))) { + item.oldTscomment = slurpEscapedString(lines, l, 11, QLatin1String("#| "), cd); + } else { + cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) + .arg(l + 1).arg(lines[l])); + error = true; + } + break; + case '~': + if (line.startsWith(QLatin1String("#~ msgid "))) { + item.msgId = slurpEscapedString(lines, l, 9, QLatin1String("#~ "), cd); + } else if (line.startsWith(QLatin1String("#~ msgid_plural "))) { + QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#~ "), cd); + if (extra != item.msgId) + item.extra[QLatin1String("po-msgid_plural")] = extra; + item.isPlural = true; + } else if (line.startsWith(QLatin1String("#~ msgctxt "))) { + item.tscomment = slurpEscapedString(lines, l, 11, QLatin1String("#~ "), cd); + } else { + cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) + .arg(l + 1).arg(lines[l])); + error = true; + } + break; + default: + cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) + .arg(l + 1).arg(lines[l])); + error = true; + break; + } + } else if (line.startsWith(QLatin1String("msgctxt "))) { + item.tscomment = slurpEscapedString(lines, l, 8, QString(), cd); + } else if (line.startsWith(QLatin1String("msgid "))) { + item.msgId = slurpEscapedString(lines, l, 6, QString(), cd); + } else if (line.startsWith(QLatin1String("msgid_plural "))) { + QString extra = slurpEscapedString(lines, l, 13, QString(), cd); + if (extra != item.msgId) + item.extra[QLatin1String("po-msgid_plural")] = extra; + item.isPlural = true; + } else { + cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n")) + .arg(l + 1).arg(lines[l])); + error = true; + } + } + return !error && cd.errors().isEmpty(); +} + +bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + bool ok = true; + QTextStream out(&dev); + //qDebug() << "OUT CODEC: " << out.codec()->name(); + + bool first = true; + if (translator.messages().isEmpty() || !translator.messages().first().sourceText().isEmpty()) { + out << + "# SOME DESCRIPTIVE TITLE.\n" + "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n" + "# This file is distributed under the same license as the PACKAGE package.\n" + "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n" + "#\n" + "#, fuzzy\n" + "msgid \"\"\n" + "msgstr \"\"\n" + "\"X-Virgin-Header: remove this line if you change anything in the header.\\n\"\n"; + if (!translator.languageCode().isEmpty()) + out << "\"X-Language: " << translator.languageCode() << "\\n\"\n"; + if (!translator.sourceLanguageCode().isEmpty()) + out << "\"X-Source-Language: " << translator.sourceLanguageCode() << "\\n\"\n"; + first = false; + } + foreach (const TranslatorMessage &msg, translator.messages()) { + if (!first) + out << endl; + + if (!msg.translatorComment().isEmpty()) + out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment()); + + if (!msg.extraComment().isEmpty()) + out << poEscapedLines(QLatin1String("#."), true, msg.extraComment()); + + if (!msg.context().isEmpty()) + out << QLatin1String("#. ts-context ") << msg.context() << '\n'; + if (!msg.id().isEmpty()) + out << QLatin1String("#. ts-id ") << msg.id() << '\n'; + + if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) { + QStringList refs; + foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) + refs.append(QString(QLatin1String("%2:%1")) + .arg(ref.lineNumber()).arg(ref.fileName())); + out << poWrappedEscapedLines(QLatin1String("#:"), true, refs.join(QLatin1String(" "))); + } + + bool noWrap = false; + QStringList flags; + if (msg.type() == TranslatorMessage::Unfinished) + flags.append(QLatin1String("fuzzy")); + TranslatorMessage::ExtraData::const_iterator itr = + msg.extras().find(QLatin1String("po-flags")); + if (itr != msg.extras().end()) { + if (itr->split(QLatin1String(", ")).contains(QLatin1String("no-wrap"))) + noWrap = true; + flags.append(*itr); + } + if (!flags.isEmpty()) + out << "#, " << flags.join(QLatin1String(", ")) << '\n'; + + QString prefix = QLatin1String("#| "); + if (!msg.oldComment().isEmpty()) + out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.oldComment()); + if (!msg.oldSourceText().isEmpty()) + out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText()); + QString plural = msg.extra(QLatin1String("po-old_msgid_plural")); + if (!plural.isEmpty()) + out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural); + prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : ""); + if (!msg.comment().isEmpty()) + out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.comment()); + out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText()); + if (!msg.isPlural()) { + QString transl = msg.translation(); + if (first) { + transl.remove(QRegExp(QLatin1String("\\bX-Language:[^\n]*\n"))); + if (!translator.languageCode().isEmpty()) + transl += QLatin1String("X-Language: ") + translator.languageCode() + QLatin1Char('\n'); + } + out << poEscapedString(prefix, QLatin1String("msgstr"), noWrap, transl); + } else { + QString plural = msg.extra(QLatin1String("po-msgid_plural")); + if (plural.isEmpty()) + plural = msg.sourceText(); + out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural); + QStringList translations = translator.normalizedTranslations(msg, cd, &ok); + for (int i = 0; i != translations.size(); ++i) { + out << poEscapedString(prefix, QString::fromLatin1("msgstr[%1]").arg(i), noWrap, + translations.at(i)); + } + } + first = false; + } + return ok; +} + +int initPO() +{ + Translator::FileFormat format; + format.extension = QLatin1String("po"); + format.description = QObject::tr("GNU Gettext localization files"); + format.loader = &loadPO; + format.saver = &savePO; + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = 1; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initPO) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/profileevaluator.cpp b/tools/linguist/shared/profileevaluator.cpp new file mode 100644 index 0000000..1e91f92 --- /dev/null +++ b/tools/linguist/shared/profileevaluator.cpp @@ -0,0 +1,1785 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "profileevaluator.h" +#include "proparserutils.h" +#include "proitems.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QList> +#include <QtCore/QRegExp> +#include <QtCore/QSet> +#include <QtCore/QStack> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QTextStream> + +#ifdef Q_OS_WIN32 +#define QT_POPEN _popen +#else +#define QT_POPEN popen +#endif + +QT_BEGIN_NAMESPACE + +/////////////////////////////////////////////////////////////////////// +// +// ProFileEvaluator::Private +// +/////////////////////////////////////////////////////////////////////// + +class ProFileEvaluator::Private : public AbstractProItemVisitor +{ +public: + Private(ProFileEvaluator *q_); + + bool read(ProFile *pro); + + ProBlock *currentBlock(); + void updateItem(); + bool parseLine(const QString &line); + void insertVariable(const QString &line, int *i); + void insertOperator(const char op); + void insertComment(const QString &comment); + void enterScope(bool multiLine); + void leaveScope(); + void finalizeBlock(); + + // implementation of AbstractProItemVisitor + bool visitBeginProBlock(ProBlock *block); + bool visitEndProBlock(ProBlock *block); + bool visitBeginProVariable(ProVariable *variable); + bool visitEndProVariable(ProVariable *variable); + bool visitBeginProFile(ProFile *value); + bool visitEndProFile(ProFile *value); + bool visitProValue(ProValue *value); + bool visitProFunction(ProFunction *function); + bool visitProOperator(ProOperator *oper); + bool visitProCondition(ProCondition *condition); + + QStringList valuesDirect(const QString &variableName) const { return m_valuemap[variableName]; } + QStringList values(const QString &variableName) const; + QStringList values(const QString &variableName, const ProFile *pro) const; + QString propertyValue(const QString &val) const; + + bool isActiveConfig(const QString &config, bool regex = false); + QStringList expandPattern(const QString &pattern); + void expandPatternHelper(const QString &relName, const QString &absName, + QStringList &sources_out); + QStringList expandVariableReferences(const QString &value); + QStringList evaluateExpandFunction(const QString &function, const QString &arguments); + QString format(const char *format) const; + + QString currentFileName() const; + QString getcwd() const; + ProFile *currentProFile() const; + + bool evaluateConditionalFunction(const QString &function, const QString &arguments, bool *result); + bool evaluateFile(const QString &fileName, bool *result); + bool evaluateFeatureFile(const QString &fileName, bool *result); + + QStringList qmakeFeaturePaths(); + + ProFileEvaluator *q; + + QStack<ProBlock *> m_blockstack; + ProBlock *m_block; + + ProItem *m_commentItem; + QString m_proitem; + QString m_pendingComment; + bool m_syntaxError; + bool m_contNextLine; + bool m_condition; + bool m_invertNext; + QString m_lastVarName; + ProVariable::VariableOperator m_variableOperator; + int m_lineNo; // Error reporting + QString m_oldPath; // To restore the current path to the path + QStack<ProFile*> m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' + + QHash<QString, QStringList> m_valuemap; // VariableName must be us-ascii, the content however can be non-us-ascii. + QHash<const ProFile*, QHash<QString, QStringList> > m_filevaluemap; // Variables per include file + QHash<QString, QString> m_properties; + QString m_origfile; + + int m_prevLineNo; // Checking whether we're assigning the same TARGET + ProFile *m_prevProFile; // See m_prevLineNo + + bool m_verbose; +}; + +ProFileEvaluator::Private::Private(ProFileEvaluator *q_) + : q(q_) +{ + m_prevLineNo = 0; + m_prevProFile = 0; + m_verbose = true; + m_block = 0; + m_commentItem = 0; + m_syntaxError = 0; + m_lineNo = 0; + m_contNextLine = false; +} + +bool ProFileEvaluator::Private::read(ProFile *pro) +{ + QFile file(pro->fileName()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + q->errorMessage(format("%1 not readable.").arg(pro->fileName())); + return false; + } + + m_syntaxError = false; + m_lineNo = 1; + m_blockstack.push(pro); + + QTextStream ts(&file); + while (!ts.atEnd()) { + QString line = ts.readLine(); + if (!parseLine(line)) { + q->errorMessage(format(".pro parse failure.")); + return false; + } + ++m_lineNo; + } + return true; +} + +bool ProFileEvaluator::Private::parseLine(const QString &line0) +{ + if (m_blockstack.isEmpty()) + return false; + + ushort quote = 0; + int parens = 0; + bool contNextLine = false; + QString line = line0.simplified(); + + for (int i = 0; !m_syntaxError && i < line.length(); ++i) { + ushort c = line.at(i).unicode(); + if (quote && c == quote) + quote = 0; + else if (c == '(') + ++parens; + else if (c == ')') + --parens; + else if (c == '"' && (i == 0 || line.at(i - 1).unicode() != '\\')) + quote = c; + else if (!parens && !quote) { + if (c == '#') { + insertComment(line.mid(i + 1)); + contNextLine = m_contNextLine; + break; + } + if (c == '\\' && i >= line.count() - 1) { + updateItem(); + contNextLine = true; + continue; + } + if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) { + if (c == ' ') + updateItem(); + else + m_proitem += c; + continue; + } + if (c == ':') { + enterScope(false); + continue; + } + if (c == '{') { + enterScope(true); + continue; + } + if (c == '}') { + leaveScope(); + continue; + } + if (c == '=') { + insertVariable(line, &i); + continue; + } + if (c == '|' || c == '!') { + insertOperator(c); + continue; + } + } + + m_proitem += c; + } + m_contNextLine = contNextLine; + + if (!m_syntaxError) { + updateItem(); + if (!m_contNextLine) + finalizeBlock(); + } + return !m_syntaxError; +} + +void ProFileEvaluator::Private::finalizeBlock() +{ + if (m_blockstack.isEmpty()) { + m_syntaxError = true; + } else { + if (m_blockstack.top()->blockKind() & ProBlock::SingleLine) + leaveScope(); + m_block = 0; + m_commentItem = 0; + } +} + +void ProFileEvaluator::Private::insertVariable(const QString &line, int *i) +{ + ProVariable::VariableOperator opkind; + + switch (m_proitem.at(m_proitem.length() - 1).unicode()) { + case '+': + m_proitem.chop(1); + opkind = ProVariable::AddOperator; + break; + case '-': + m_proitem.chop(1); + opkind = ProVariable::RemoveOperator; + break; + case '*': + m_proitem.chop(1); + opkind = ProVariable::UniqueAddOperator; + break; + case '~': + m_proitem.chop(1); + opkind = ProVariable::ReplaceOperator; + break; + default: + opkind = ProVariable::SetOperator; + } + + ProBlock *block = m_blockstack.top(); + m_proitem = m_proitem.trimmed(); + ProVariable *variable = new ProVariable(m_proitem, block); + variable->setLineNumber(m_lineNo); + variable->setVariableOperator(opkind); + block->appendItem(variable); + m_block = variable; + + if (!m_pendingComment.isEmpty()) { + m_block->setComment(m_pendingComment); + m_pendingComment.clear(); + } + m_commentItem = variable; + + m_proitem.clear(); + + if (opkind == ProVariable::ReplaceOperator) { + // skip util end of line or comment + while (1) { + ++(*i); + + // end of line? + if (*i >= line.count()) + break; + + // comment? + if (line.at(*i).unicode() == '#') { + --(*i); + break; + } + + m_proitem += line.at(*i); + } + m_proitem = m_proitem.trimmed(); + } +} + +void ProFileEvaluator::Private::insertOperator(const char op) +{ + updateItem(); + + ProOperator::OperatorKind opkind; + switch(op) { + case '!': + opkind = ProOperator::NotOperator; + break; + case '|': + opkind = ProOperator::OrOperator; + break; + default: + opkind = ProOperator::OrOperator; + } + + ProBlock * const block = currentBlock(); + ProOperator * const proOp = new ProOperator(opkind); + proOp->setLineNumber(m_lineNo); + block->appendItem(proOp); + m_commentItem = proOp; +} + +void ProFileEvaluator::Private::insertComment(const QString &comment) +{ + updateItem(); + + QString strComment; + if (!m_commentItem) + strComment = m_pendingComment; + else + strComment = m_commentItem->comment(); + + if (strComment.isEmpty()) + strComment = comment; + else { + strComment += QLatin1Char('\n'); + strComment += comment.trimmed(); + } + + strComment = strComment.trimmed(); + + if (!m_commentItem) + m_pendingComment = strComment; + else + m_commentItem->setComment(strComment); +} + +void ProFileEvaluator::Private::enterScope(bool multiLine) +{ + updateItem(); + + ProBlock *parent = currentBlock(); + ProBlock *block = new ProBlock(parent); + block->setLineNumber(m_lineNo); + parent->setBlockKind(ProBlock::ScopeKind); + + parent->appendItem(block); + + if (multiLine) + block->setBlockKind(ProBlock::ScopeContentsKind); + else + block->setBlockKind(ProBlock::ScopeContentsKind|ProBlock::SingleLine); + + m_blockstack.push(block); + m_block = 0; +} + +void ProFileEvaluator::Private::leaveScope() +{ + updateItem(); + m_blockstack.pop(); + finalizeBlock(); +} + +ProBlock *ProFileEvaluator::Private::currentBlock() +{ + if (m_block) + return m_block; + + ProBlock *parent = m_blockstack.top(); + m_block = new ProBlock(parent); + m_block->setLineNumber(m_lineNo); + parent->appendItem(m_block); + + if (!m_pendingComment.isEmpty()) { + m_block->setComment(m_pendingComment); + m_pendingComment.clear(); + } + + m_commentItem = m_block; + + return m_block; +} + +void ProFileEvaluator::Private::updateItem() +{ + m_proitem = m_proitem.trimmed(); + if (m_proitem.isEmpty()) + return; + + ProBlock *block = currentBlock(); + if (block->blockKind() & ProBlock::VariableKind) { + m_commentItem = new ProValue(m_proitem, static_cast<ProVariable*>(block)); + } else if (m_proitem.endsWith(QLatin1Char(')'))) { + m_commentItem = new ProFunction(m_proitem); + } else { + m_commentItem = new ProCondition(m_proitem); + } + m_commentItem->setLineNumber(m_lineNo); + block->appendItem(m_commentItem); + + m_proitem.clear(); +} + + +bool ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block) +{ + if (block->blockKind() == ProBlock::ScopeKind) { + m_invertNext = false; + m_condition = false; + } + return true; +} + +bool ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) +{ + Q_UNUSED(block); + return true; +} + +bool ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) +{ + m_lastVarName = variable->variable(); + m_variableOperator = variable->variableOperator(); + return true; +} + +bool ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable) +{ + Q_UNUSED(variable); + m_lastVarName.clear(); + return true; +} + +bool ProFileEvaluator::Private::visitProOperator(ProOperator *oper) +{ + m_invertNext = (oper->operatorKind() == ProOperator::NotOperator); + return true; +} + +bool ProFileEvaluator::Private::visitProCondition(ProCondition *cond) +{ + if (!m_condition) { + if (m_invertNext) + m_condition |= !isActiveConfig(cond->text(), true); + else + m_condition |= isActiveConfig(cond->text(), true); + } + return true; +} + +bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro) +{ + PRE(pro); + bool ok = true; + m_lineNo = pro->lineNumber(); + if (m_oldPath.isEmpty()) { + // change the working directory for the initial profile we visit, since + // that is *the* profile. All the other times we reach this function will be due to + // include(file) or load(file) + m_oldPath = QDir::currentPath(); + m_profileStack.push(pro); + ok = QDir::setCurrent(pro->directoryName()); + } + + if (m_origfile.isEmpty()) + m_origfile = pro->fileName(); + + return ok; +} + +bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro) +{ + PRE(pro); + bool ok = true; + m_lineNo = pro->lineNumber(); + if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) { + m_profileStack.pop(); + ok = QDir::setCurrent(m_oldPath); + } + return ok; +} + +bool ProFileEvaluator::Private::visitProValue(ProValue *value) +{ + PRE(value); + m_lineNo = value->lineNumber(); + QString val = value->value(); + + QString varName = m_lastVarName; + + QStringList v = expandVariableReferences(val); + + // Since qmake combines different values for the TARGET variable, we join + // TARGET values that are on the same line. We can't do this later with all + // values because this parser isn't scope-aware, so we'd risk joining + // scope-specific targets together. + if (varName == QLatin1String("TARGET") + && m_lineNo == m_prevLineNo + && currentProFile() == m_prevProFile) { + QStringList targets = m_valuemap.value(QLatin1String("TARGET")); + m_valuemap.remove(QLatin1String("TARGET")); + QStringList lastTarget(targets.takeLast()); + lastTarget << v.join(QLatin1String(" ")); + targets.push_back(lastTarget.join(QLatin1String(" "))); + v = targets; + } + m_prevLineNo = m_lineNo; + m_prevProFile = currentProFile(); + + // The following two blocks fix bug 180128 by making all "interesting" + // file name absolute in each .pro file, not just the top most one + if (varName == QLatin1String("SOURCES") + || varName == QLatin1String("HEADERS") + || varName == QLatin1String("INTERFACES") + || varName == QLatin1String("FORMS") + || varName == QLatin1String("FORMS3") + || varName == QLatin1String("RESOURCES")) { + // matches only existent files, expand certain(?) patterns + QStringList vv; + for (int i = v.count(); --i >= 0; ) + vv << expandPattern(v[i]); + v = vv; + } + + if (varName == QLatin1String("TRANSLATIONS")) { + // also matches non-existent files, but does not expand pattern + QString dir = QFileInfo(currentFileName()).absolutePath(); + dir += QLatin1Char('/'); + for (int i = v.count(); --i >= 0; ) + v[i] = QFileInfo(dir, v[i]).absoluteFilePath(); + } + + switch (m_variableOperator) { + case ProVariable::UniqueAddOperator: // * + insertUnique(&m_valuemap, varName, v, true); + insertUnique(&m_filevaluemap[currentProFile()], varName, v, true); + break; + case ProVariable::SetOperator: // = + case ProVariable::AddOperator: // + + insertUnique(&m_valuemap, varName, v, false); + insertUnique(&m_filevaluemap[currentProFile()], varName, v, false); + break; + case ProVariable::RemoveOperator: // - + // fix me: interaction between AddOperator and RemoveOperator + insertUnique(&m_valuemap, varName.prepend(QLatin1Char('-')), v, false); + insertUnique(&m_filevaluemap[currentProFile()], + varName.prepend(QLatin1Char('-')), v, false); + break; + case ProVariable::ReplaceOperator: // ~ + { + // DEFINES ~= s/a/b/?[gqi] + +/* Create a superset by executing replacement + adding items that have changed + to original list. We're not sure if this is really the right approach, so for + the time being we will just do nothing ... + + QChar sep = val.at(1); + QStringList func = val.split(sep); + if (func.count() < 3 || func.count() > 4) { + q->logMessage(format("'~= operator '(function s///) expects 3 or 4 arguments.")); + return false; + } + if (func[0] != QLatin1String("s")) { + q->logMessage(format("~= operator can only handle s/// function.")); + return false; + } + + bool global = false, quote = false, case_sense = false; + if (func.count() == 4) { + global = func[3].indexOf(QLatin1Char('g')) != -1; + case_sense = func[3].indexOf(QLatin1Char('i')) == -1; + quote = func[3].indexOf(QLatin1Char('q')) != -1; + } + QString pattern = func[1]; + QString replace = func[2]; + if (quote) + pattern = QRegExp::escape(pattern); + + QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); + + QStringList replaceList = replaceInList(m_valuemap.value(varName), regexp, replace, + global); + // Add changed entries to list + foreach (const QString &entry, replaceList) + if (!m_valuemap.value(varName).contains(entry)) + insertUnique(&m_valuemap, varName, QStringList() << entry, false); + + replaceList = replaceInList(m_filevaluemap[currentProFile()].value(varName), regexp, + replace, global); + foreach (const QString &entry, replaceList) + if (!m_filevaluemap[currentProFile()].value(varName).contains(entry)) + insertUnique(&m_filevaluemap[currentProFile()], varName, + QStringList() << entry, false); */ + } + break; + + } + return true; +} + +bool ProFileEvaluator::Private::visitProFunction(ProFunction *func) +{ + m_lineNo = func->lineNumber(); + bool result = true; + bool ok = true; + QString text = func->text(); + int lparen = text.indexOf(QLatin1Char('(')); + int rparen = text.lastIndexOf(QLatin1Char(')')); + Q_ASSERT(lparen < rparen); + + QString arguments = text.mid(lparen + 1, rparen - lparen - 1); + QString funcName = text.left(lparen); + ok &= evaluateConditionalFunction(funcName.trimmed(), arguments, &result); + return ok; +} + + +QStringList ProFileEvaluator::Private::qmakeFeaturePaths() +{ + QStringList concat; + { + const QString base_concat = QDir::separator() + QString(QLatin1String("features")); + concat << base_concat + QDir::separator() + QLatin1String("mac"); + concat << base_concat + QDir::separator() + QLatin1String("macx"); + concat << base_concat + QDir::separator() + QLatin1String("unix"); + concat << base_concat + QDir::separator() + QLatin1String("win32"); + concat << base_concat + QDir::separator() + QLatin1String("mac9"); + concat << base_concat + QDir::separator() + QLatin1String("qnx6"); + concat << base_concat; + } + const QString mkspecs_concat = QDir::separator() + QString(QLatin1String("mkspecs")); + QStringList feature_roots; + QByteArray mkspec_path = qgetenv("QMAKEFEATURES"); + if (!mkspec_path.isNull()) + feature_roots += splitPathList(QString::fromLocal8Bit(mkspec_path)); + /* + if (prop) + feature_roots += splitPathList(prop->value("QMAKEFEATURES")); + if (!Option::mkfile::cachefile.isEmpty()) { + QString path; + int last_slash = Option::mkfile::cachefile.lastIndexOf(Option::dir_sep); + if (last_slash != -1) + path = Option::fixPathToLocalOS(Option::mkfile::cachefile.left(last_slash)); + foreach (const QString &concat_it, concat) + feature_roots << (path + concat_it); + } + */ + + QByteArray qmakepath = qgetenv("QMAKEPATH"); + if (!qmakepath.isNull()) { + const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath)); + foreach (const QString &item, lst) { + foreach (const QString &concat_it, concat) + feature_roots << (item + mkspecs_concat + concat_it); + } + } + //if (!Option::mkfile::qmakespec.isEmpty()) + // feature_roots << Option::mkfile::qmakespec + QDir::separator() + "features"; + //if (!Option::mkfile::qmakespec.isEmpty()) { + // QFileInfo specfi(Option::mkfile::qmakespec); + // QDir specdir(specfi.absoluteFilePath()); + // while (!specdir.isRoot()) { + // if (!specdir.cdUp() || specdir.isRoot()) + // break; + // if (QFile::exists(specdir.path() + QDir::separator() + "features")) { + // foreach (const QString &concat_it, concat) + // feature_roots << (specdir.path() + concat_it); + // break; + // } + // } + //} + foreach (const QString &concat_it, concat) + feature_roots << (propertyValue(QLatin1String("QT_INSTALL_PREFIX")) + + mkspecs_concat + concat_it); + foreach (const QString &concat_it, concat) + feature_roots << (propertyValue(QLatin1String("QT_INSTALL_DATA")) + + mkspecs_concat + concat_it); + return feature_roots; +} + +QString ProFileEvaluator::Private::propertyValue(const QString &name) const +{ + if (m_properties.contains(name)) + return m_properties.value(name); + if (name == QLatin1String("QT_INSTALL_PREFIX")) + return QLibraryInfo::location(QLibraryInfo::PrefixPath); + if (name == QLatin1String("QT_INSTALL_DATA")) + return QLibraryInfo::location(QLibraryInfo::DataPath); + if (name == QLatin1String("QT_INSTALL_DOCS")) + return QLibraryInfo::location(QLibraryInfo::DocumentationPath); + if (name == QLatin1String("QT_INSTALL_HEADERS")) + return QLibraryInfo::location(QLibraryInfo::HeadersPath); + if (name == QLatin1String("QT_INSTALL_LIBS")) + return QLibraryInfo::location(QLibraryInfo::LibrariesPath); + if (name == QLatin1String("QT_INSTALL_BINS")) + return QLibraryInfo::location(QLibraryInfo::BinariesPath); + if (name == QLatin1String("QT_INSTALL_PLUGINS")) + return QLibraryInfo::location(QLibraryInfo::PluginsPath); + if (name == QLatin1String("QT_INSTALL_TRANSLATIONS")) + return QLibraryInfo::location(QLibraryInfo::TranslationsPath); + if (name == QLatin1String("QT_INSTALL_CONFIGURATION")) + return QLibraryInfo::location(QLibraryInfo::SettingsPath); + if (name == QLatin1String("QT_INSTALL_EXAMPLES")) + return QLibraryInfo::location(QLibraryInfo::ExamplesPath); + if (name == QLatin1String("QT_INSTALL_DEMOS")) + return QLibraryInfo::location(QLibraryInfo::DemosPath); + if (name == QLatin1String("QMAKE_MKSPECS")) + return qmake_mkspec_paths().join(Option::dirlist_sep); + if (name == QLatin1String("QMAKE_VERSION")) + return QLatin1String("1.0"); //### FIXME + //return qmake_version(); +#ifdef QT_VERSION_STR + if (name == QLatin1String("QT_VERSION")) + return QLatin1String(QT_VERSION_STR); +#endif + return QLatin1String("UNKNOWN"); //### +} + +ProFile *ProFileEvaluator::Private::currentProFile() const +{ + if (m_profileStack.count() > 0) + return m_profileStack.top(); + return 0; +} + +QString ProFileEvaluator::Private::currentFileName() const +{ + ProFile *pro = currentProFile(); + if (pro) + return pro->fileName(); + return QString(); +} + +QString ProFileEvaluator::Private::getcwd() const +{ + ProFile *cur = m_profileStack.top(); + return cur->directoryName(); +} + +QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str) +{ + QStringList ret; +// if (ok) +// *ok = true; + if (str.isEmpty()) + return ret; + + const ushort LSQUARE = '['; + const ushort RSQUARE = ']'; + const ushort LCURLY = '{'; + const ushort RCURLY = '}'; + const ushort LPAREN = '('; + const ushort RPAREN = ')'; + const ushort DOLLAR = '$'; + const ushort BACKSLASH = '\\'; + const ushort UNDERSCORE = '_'; + const ushort DOT = '.'; + const ushort SPACE = ' '; + const ushort TAB = '\t'; + const ushort SINGLEQUOTE = '\''; + const ushort DOUBLEQUOTE = '"'; + + ushort unicode, quote = 0; + const QChar *str_data = str.data(); + const int str_len = str.length(); + + ushort term; + QString var, args; + + int replaced = 0; + QString current; + for (int i = 0; i < str_len; ++i) { + unicode = str_data[i].unicode(); + const int start_var = i; + if (unicode == DOLLAR && str_len > i+2) { + unicode = str_data[++i].unicode(); + if (unicode == DOLLAR) { + term = 0; + var.clear(); + args.clear(); + enum { VAR, ENVIRON, FUNCTION, PROPERTY } var_type = VAR; + unicode = str_data[++i].unicode(); + if (unicode == LSQUARE) { + unicode = str_data[++i].unicode(); + term = RSQUARE; + var_type = PROPERTY; + } else if (unicode == LCURLY) { + unicode = str_data[++i].unicode(); + var_type = VAR; + term = RCURLY; + } else if (unicode == LPAREN) { + unicode = str_data[++i].unicode(); + var_type = ENVIRON; + term = RPAREN; + } + forever { + if (!(unicode & (0xFF<<8)) && + unicode != DOT && unicode != UNDERSCORE && + //unicode != SINGLEQUOTE && unicode != DOUBLEQUOTE && + (unicode < 'a' || unicode > 'z') && (unicode < 'A' || unicode > 'Z') && + (unicode < '0' || unicode > '9')) + break; + var.append(QChar(unicode)); + if (++i == str_len) + break; + unicode = str_data[i].unicode(); + // at this point, i points to either the 'term' or 'next' character (which is in unicode) + } + if (var_type == VAR && unicode == LPAREN) { + var_type = FUNCTION; + int depth = 0; + forever { + if (++i == str_len) + break; + unicode = str_data[i].unicode(); + if (unicode == LPAREN) { + depth++; + } else if (unicode == RPAREN) { + if (!depth) + break; + --depth; + } + args.append(QChar(unicode)); + } + if (++i < str_len) + unicode = str_data[i].unicode(); + else + unicode = 0; + // at this point i is pointing to the 'next' character (which is in unicode) + // this might actually be a term character since you can do $${func()} + } + if (term) { + if (unicode != term) { + q->logMessage(format("Missing %1 terminator [found %2]") + .arg(QChar(term)) + .arg(unicode ? QString(unicode) : QString::fromLatin1(("end-of-line")))); +// if (ok) +// *ok = false; + return QStringList(); + } + } else { + // move the 'cursor' back to the last char of the thing we were looking at + --i; + } + // since i never points to the 'next' character, there is no reason for this to be set + unicode = 0; + + QStringList replacement; + if (var_type == ENVIRON) { + replacement = split_value_list(QString::fromLocal8Bit(qgetenv(var.toLatin1().constData()))); + } else if (var_type == PROPERTY) { + replacement << propertyValue(var); + } else if (var_type == FUNCTION) { + replacement << evaluateExpandFunction(var, args); + } else if (var_type == VAR) { + replacement = values(var); + } + if (!(replaced++) && start_var) + current = str.left(start_var); + if (!replacement.isEmpty()) { + if (quote) { + current += replacement.join(QString(Option::field_sep)); + } else { + current += replacement.takeFirst(); + if (!replacement.isEmpty()) { + if (!current.isEmpty()) + ret.append(current); + current = replacement.takeLast(); + if (!replacement.isEmpty()) + ret += replacement; + } + } + } + } else { + if (replaced) + current.append(QLatin1Char('$')); + } + } + if (quote && unicode == quote) { + unicode = 0; + quote = 0; + } else if (unicode == BACKSLASH) { + bool escape = false; + const char *symbols = "[]{}()$\\'\""; + for (const char *s = symbols; *s; ++s) { + if (str_data[i+1].unicode() == (ushort)*s) { + i++; + escape = true; + if (!(replaced++)) + current = str.left(start_var); + current.append(str.at(i)); + break; + } + } + if (escape || !replaced) + unicode =0; + } else if (!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) { + quote = unicode; + unicode = 0; + if (!(replaced++) && i) + current = str.left(i); + } else if (!quote && (unicode == SPACE || unicode == TAB)) { + unicode = 0; + if (!current.isEmpty()) { + ret.append(current); + current.clear(); + } + } + if (replaced && unicode) + current.append(QChar(unicode)); + } + if (!replaced) + ret = QStringList(str); + else if (!current.isEmpty()) + ret.append(current); + return ret; +} + +bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex) +{ + // magic types for easy flipping + if (config == QLatin1String("true")) + return true; + if (config == QLatin1String("false")) + return false; + + // mkspecs + if ((Option::target_mode == Option::TARG_MACX_MODE + || Option::target_mode == Option::TARG_QNX6_MODE + || Option::target_mode == Option::TARG_UNIX_MODE) + && config == QLatin1String("unix")) + return true; + if (Option::target_mode == Option::TARG_MACX_MODE && config == QLatin1String("macx")) + return true; + if (Option::target_mode == Option::TARG_QNX6_MODE && config == QLatin1String("qnx6")) + return true; + if (Option::target_mode == Option::TARG_MAC9_MODE && config == QLatin1String("mac9")) + return true; + if ((Option::target_mode == Option::TARG_MAC9_MODE + || Option::target_mode == Option::TARG_MACX_MODE) + && config == QLatin1String("mac")) + return true; + if (Option::target_mode == Option::TARG_WIN_MODE && config == QLatin1String("win32")) + return true; + + QRegExp re(config, Qt::CaseSensitive, QRegExp::Wildcard); + QString spec = Option::qmakespec; + if ((regex && re.exactMatch(spec)) || (!regex && spec == config)) + return true; + + return false; +} + +QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments) +{ + QStringList argumentsList = split_arg_list(arguments); + QStringList args; + for (int i = 0; i < argumentsList.count(); ++i) + args += expandVariableReferences(argumentsList[i]); + + enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, E_CAT, E_FROMFILE, E_EVAL, E_LIST, + E_SPRINTF, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION, + E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND, + E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, + E_REPLACE }; + + static QHash<QString, int> *expands = 0; + if (!expands) { + expands = new QHash<QString, int>; + expands->insert(QLatin1String("member"), E_MEMBER); //v (implemented) + expands->insert(QLatin1String("first"), E_FIRST); //v + expands->insert(QLatin1String("last"), E_LAST); //v + expands->insert(QLatin1String("cat"), E_CAT); + expands->insert(QLatin1String("fromfile"), E_FROMFILE); + expands->insert(QLatin1String("eval"), E_EVAL); + expands->insert(QLatin1String("list"), E_LIST); + expands->insert(QLatin1String("sprintf"), E_SPRINTF); + expands->insert(QLatin1String("join"), E_JOIN); //v + expands->insert(QLatin1String("split"), E_SPLIT); //v + expands->insert(QLatin1String("basename"), E_BASENAME); //v + expands->insert(QLatin1String("dirname"), E_DIRNAME); //v + expands->insert(QLatin1String("section"), E_SECTION); + expands->insert(QLatin1String("find"), E_FIND); + expands->insert(QLatin1String("system"), E_SYSTEM); //v + expands->insert(QLatin1String("unique"), E_UNIQUE); + expands->insert(QLatin1String("quote"), E_QUOTE); //v + expands->insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND); + expands->insert(QLatin1String("upper"), E_UPPER); + expands->insert(QLatin1String("lower"), E_LOWER); + expands->insert(QLatin1String("re_escape"), E_RE_ESCAPE); + expands->insert(QLatin1String("files"), E_FILES); + expands->insert(QLatin1String("prompt"), E_PROMPT); + expands->insert(QLatin1String("replace"), E_REPLACE); + } + ExpandFunc func_t = ExpandFunc(expands->value(func.toLower())); + + QStringList ret; + + switch (func_t) { + case E_BASENAME: + case E_DIRNAME: + case E_SECTION: { + bool regexp = false; + QString sep, var; + int beg = 0; + int end = -1; + if (func_t == E_SECTION) { + if (args.count() != 3 && args.count() != 4) { + q->logMessage(format("%1(var) section(var, sep, begin, end) " + "requires three or four arguments.").arg(func)); + } else { + var = args[0]; + sep = args[1]; + beg = args[2].toInt(); + if (args.count() == 4) + end = args[3].toInt(); + } + } else { + if (args.count() != 1) { + q->logMessage(format("%1(var) requires one argument.").arg(func)); + } else { + var = args[0]; + regexp = true; + sep = QLatin1String("[\\\\/]"); + if (func_t == E_DIRNAME) + end = -2; + else + beg = -1; + } + } + if (!var.isNull()) { + foreach (const QString str, values(var)) { + if (regexp) + ret += str.section(QRegExp(sep), beg, end); + else + ret += str.section(sep, beg, end); + } + } + break; + } + case E_JOIN: { + if (args.count() < 1 || args.count() > 4) { + q->logMessage(format("join(var, glue, before, after) requires one to four arguments.")); + } else { + QString glue, before, after; + if (args.count() >= 2) + glue = args[1]; + if (args.count() >= 3) + before = args[2]; + if (args.count() == 4) + after = args[3]; + const QStringList &var = values(args.first()); + if (!var.isEmpty()) + ret.append(before + var.join(glue) + after); + } + break; + } + case E_SPLIT: { + if (args.count() != 2) { + q->logMessage(format("split(var, sep) requires two arguments")); + } else { + QString sep = args.at(1); + foreach (const QString &var, values(args.first())) + foreach (const QString &splt, var.split(sep)) + ret.append(splt); + } + break; + } + case E_MEMBER: { + if (args.count() < 1 || args.count() > 3) { + q->logMessage(format("member(var, start, end) requires one to three arguments.")); + } else { + bool ok = true; + const QStringList var = values(args.first()); + int start = 0, end = 0; + if (args.count() >= 2) { + QString start_str = args[1]; + start = start_str.toInt(&ok); + if (!ok) { + if (args.count() == 2) { + int dotdot = start_str.indexOf(QLatin1String("..")); + if (dotdot != -1) { + start = start_str.left(dotdot).toInt(&ok); + if (ok) + end = start_str.mid(dotdot+2).toInt(&ok); + } + } + if (!ok) + q->logMessage(format("member() argument 2 (start) '%2' invalid.") + .arg(start_str)); + } else { + end = start; + if (args.count() == 3) + end = args[2].toInt(&ok); + if (!ok) + q->logMessage(format("member() argument 3 (end) '%2' invalid.\n") + .arg(args[2])); + } + } + if (ok) { + if (start < 0) + start += var.count(); + if (end < 0) + end += var.count(); + if (start < 0 || start >= var.count() || end < 0 || end >= var.count()) { + //nothing + } else if (start < end) { + for (int i = start; i <= end && var.count() >= i; i++) + ret.append(var[i]); + } else { + for (int i = start; i >= end && var.count() >= i && i >= 0; i--) + ret += var[i]; + } + } + } + break; + } + case E_FIRST: + case E_LAST: { + if (args.count() != 1) { + q->logMessage(format("%1(var) requires one argument.").arg(func)); + } else { + const QStringList var = values(args.first()); + if (!var.isEmpty()) { + if (func_t == E_FIRST) + ret.append(var[0]); + else + ret.append(var.last()); + } + } + break; + } + case E_SYSTEM: { + if (m_condition) { + if (args.count() < 1 || args.count() > 2) { + q->logMessage(format("system(execute) requires one or two arguments.")); + } else { + char buff[256]; + FILE *proc = QT_POPEN(args[0].toLatin1(), "r"); + bool singleLine = true; + if (args.count() > 1) + singleLine = (args[1].toLower() == QLatin1String("true")); + QString output; + while (proc && !feof(proc)) { + int read_in = int(fread(buff, 1, 255, proc)); + if (!read_in) + break; + for (int i = 0; i < read_in; i++) { + if ((singleLine && buff[i] == '\n') || buff[i] == '\t') + buff[i] = ' '; + } + buff[read_in] = '\0'; + output += QLatin1String(buff); + } + ret += split_value_list(output); + } + } + break; } + case E_QUOTE: + for (int i = 0; i < args.count(); ++i) + ret += QStringList(args.at(i)); + break; + case 0: + q->logMessage(format("'%1' is not a function").arg(func)); + break; + default: + q->logMessage(format("Function '%1' is not implemented").arg(func)); + break; + } + + return ret; +} + +bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &function, + const QString &arguments, bool *result) +{ + QStringList argumentsList = split_arg_list(arguments); + QString sep; + sep.append(Option::field_sep); + + QStringList args; + for (int i = 0; i < argumentsList.count(); ++i) + args += expandVariableReferences(argumentsList[i]).join(sep); + + enum ConditionFunc { CF_CONFIG = 1, CF_CONTAINS, CF_COUNT, CF_EXISTS, CF_INCLUDE, + CF_LOAD, CF_ISEMPTY, CF_SYSTEM, CF_MESSAGE}; + + static QHash<QString, int> *functions = 0; + if (!functions) { + functions = new QHash<QString, int>; + functions->insert(QLatin1String("load"), CF_LOAD); //v + functions->insert(QLatin1String("include"), CF_INCLUDE); //v + functions->insert(QLatin1String("message"), CF_MESSAGE); //v + functions->insert(QLatin1String("warning"), CF_MESSAGE); //v + functions->insert(QLatin1String("error"), CF_MESSAGE); //v + } + + bool cond = false; + bool ok = true; + + ConditionFunc func_t = (ConditionFunc)functions->value(function); + + switch (func_t) { + case CF_CONFIG: { + if (args.count() < 1 || args.count() > 2) { + q->logMessage(format("CONFIG(config) requires one or two arguments.")); + ok = false; + break; + } + if (args.count() == 1) { + //cond = isActiveConfig(args.first()); XXX + break; + } + const QStringList mutuals = args[1].split(QLatin1Char('|')); + const QStringList &configs = valuesDirect(QLatin1String("CONFIG")); + for (int i = configs.size() - 1 && ok; i >= 0; i--) { + for (int mut = 0; mut < mutuals.count(); mut++) { + if (configs[i] == mutuals[mut].trimmed()) { + cond = (configs[i] == args[0]); + break; + } + } + } + break; + } + case CF_CONTAINS: { + if (args.count() < 2 || args.count() > 3) { + q->logMessage(format("contains(var, val) requires two or three arguments.")); + ok = false; + break; + } + + QRegExp regx(args[1]); + const QStringList &l = values(args.first()); + if (args.count() == 2) { + for (int i = 0; i < l.size(); ++i) { + const QString val = l[i]; + if (regx.exactMatch(val) || val == args[1]) { + cond = true; + break; + } + } + } else { + const QStringList mutuals = args[2].split(QLatin1Char('|')); + for (int i = l.size() - 1; i >= 0; i--) { + const QString val = l[i]; + for (int mut = 0; mut < mutuals.count(); mut++) { + if (val == mutuals[mut].trimmed()) { + cond = (regx.exactMatch(val) || val == args[1]); + break; + } + } + } + } + + break; + } + case CF_COUNT: { + if (args.count() != 2 && args.count() != 3) { + q->logMessage(format("count(var, count) requires two or three arguments.")); + ok = false; + break; + } + if (args.count() == 3) { + QString comp = args[2]; + if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) { + cond = values(args.first()).count() > args[1].toInt(); + } else if (comp == QLatin1String(">=")) { + cond = values(args.first()).count() >= args[1].toInt(); + } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) { + cond = values(args.first()).count() < args[1].toInt(); + } else if (comp == QLatin1String("<=")) { + cond = values(args.first()).count() <= args[1].toInt(); + } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") || comp == QLatin1String("=") || comp == QLatin1String("==")) { + cond = values(args.first()).count() == args[1].toInt(); + } else { + ok = false; + q->logMessage(format("unexpected modifier to count(%2)").arg(comp)); + } + break; + } + cond = values(args.first()).count() == args[1].toInt(); + break; + } + case CF_INCLUDE: { + QString parseInto; + if (args.count() == 2) { + parseInto = args[1]; + } else if (args.count() != 1) { + q->logMessage(format("include(file) requires one or two arguments.")); + ok = false; + break; + } + QString fileName = args.first(); + // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style. + QDir currentProPath(getcwd()); + fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName)); + ok = evaluateFile(fileName, &ok); + break; + } + case CF_LOAD: { + QString parseInto; + bool ignore_error = false; + if (args.count() == 2) { + QString sarg = args[1]; + ignore_error = (sarg.toLower() == QLatin1String("true") || sarg.toInt()); + } else if (args.count() != 1) { + q->logMessage(format("load(feature) requires one or two arguments.")); + ok = false; + break; + } + ok = evaluateFeatureFile( args.first(), &cond); + break; + } + case CF_MESSAGE: { + if (args.count() != 1) { + q->logMessage(format("%1(message) requires one argument.").arg(function)); + ok = false; + break; + } + QString msg = args.first(); + if (function == QLatin1String("error")) { + QStringList parents; + foreach (ProFile *proFile, m_profileStack) + parents.append(proFile->fileName()); + if (!parents.isEmpty()) + parents.takeLast(); + if (parents.isEmpty()) + q->fileMessage(format("Project ERROR: %1").arg(msg)); + else + q->fileMessage(format("Project ERROR: %1. File was included from: '%2'") + .arg(msg).arg(parents.join(QLatin1String("', '")))); + } else { + q->fileMessage(format("Project MESSAGE: %1").arg(msg)); + } + break; + } + case CF_SYSTEM: { + if (args.count() != 1) { + q->logMessage(format("system(exec) requires one argument.")); + ok = false; + break; + } + ok = system(args.first().toLatin1().constData()) == 0; + break; + } + case CF_ISEMPTY: { + if (args.count() != 1) { + q->logMessage(format("isEmpty(var) requires one argument.")); + ok = false; + break; + } + QStringList sl = values(args.first()); + if (sl.count() == 0) { + cond = true; + } else if (sl.count() > 0) { + QString var = sl.first(); + cond = (var.isEmpty()); + } + break; + } + case CF_EXISTS: { + if (args.count() != 1) { + q->logMessage(format("exists(file) requires one argument.")); + ok = false; + break; + } + QString file = args.first(); + + file = QDir::cleanPath(file); + + if (QFile::exists(file)) { + cond = true; + break; + } + //regular expression I guess + QString dirstr = getcwd(); + int slsh = file.lastIndexOf(Option::dir_sep); + if (slsh != -1) { + dirstr = file.left(slsh+1); + file = file.right(file.length() - slsh - 1); + } + cond = QDir(dirstr).entryList(QStringList(file)).count(); + + break; + } + } + + if (result) + *result = cond; + + return ok; +} + +QStringList ProFileEvaluator::Private::values(const QString &variableName) const +{ + if (variableName == QLatin1String("TARGET")) { + QStringList list = m_valuemap.value(variableName); + if (!m_origfile.isEmpty()) + list.append(QFileInfo(m_origfile).baseName()); + return list; + } + if (variableName == QLatin1String("PWD")) { + return QStringList(getcwd()); + } + return m_valuemap.value(variableName); +} + +QStringList ProFileEvaluator::Private::values(const QString &variableName, const ProFile *pro) const +{ + if (variableName == QLatin1String("TARGET")) { + QStringList list = m_filevaluemap[pro].value(variableName); + if (!m_origfile.isEmpty()) + list.append(QFileInfo(m_origfile).baseName()); + return list; + } + if (variableName == QLatin1String("PWD")) { + return QStringList(QFileInfo(pro->fileName()).absoluteFilePath()); + } + return m_filevaluemap[pro].value(variableName); +} + +ProFile *ProFileEvaluator::parsedProFile(const QString &fileName) +{ + QFileInfo fi(fileName); + if (fi.exists()) { + ProFile *pro = new ProFile(fi.absoluteFilePath()); + if (d->read(pro)) + return pro; + delete pro; + } + return 0; +} + +void ProFileEvaluator::releaseParsedProFile(ProFile *proFile) +{ + delete proFile; +} + +bool ProFileEvaluator::Private::evaluateFile(const QString &fileName, bool *result) +{ + bool ok = true; + ProFile *pro = q->parsedProFile(fileName); + if (pro) { + m_profileStack.push(pro); + ok = (currentProFile() ? pro->Accept(this) : false); + m_profileStack.pop(); + q->releaseParsedProFile(pro); + + if (result) + *result = true; + } else { + if (result) + *result = false; + } +/* if (ok && readFeatures) { + QStringList configs = values("CONFIG"); + QSet<QString> processed; + foreach (const QString &fn, configs) { + if (!processed.contains(fn)) { + processed.insert(fn); + evaluateFeatureFile(fn, 0); + } + } + } */ + + return ok; +} + +bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName, bool *result) +{ + QString fn; + foreach (const QString &path, qmakeFeaturePaths()) { + QString fname = path + QLatin1Char('/') + fileName; + if (QFileInfo(fname).exists()) { + fn = fname; + break; + } + fname += QLatin1String(".prf"); + if (QFileInfo(fname).exists()) { + fn = fname; + break; + } + } + return fn.isEmpty() ? false : evaluateFile(fn, result); +} + +void ProFileEvaluator::Private::expandPatternHelper(const QString &relName, const QString &absName, + QStringList &sources_out) +{ + const QStringList vpaths = values(QLatin1String("VPATH")) + + values(QLatin1String("QMAKE_ABSOLUTE_SOURCE_PATH")) + + values(QLatin1String("DEPENDPATH")) + + values(QLatin1String("VPATH_SOURCES")); + + QFileInfo fi(absName); + bool found = fi.exists(); + // Search in all vpaths + if (!found) { + foreach (const QString &vpath, vpaths) { + fi.setFile(vpath + QDir::separator() + relName); + if (fi.exists()) { + found = true; + break; + } + } + } + + if (found) { + sources_out += fi.absoluteFilePath(); // Not resolving symlinks + } else { + QString val = relName; + QString dir; + QString wildcard = val; + QString real_dir; + if (wildcard.lastIndexOf(QLatin1Char('/')) != -1) { + dir = wildcard.left(wildcard.lastIndexOf(QLatin1Char('/')) + 1); + real_dir = dir; + wildcard = wildcard.right(wildcard.length() - dir.length()); + } + + if (real_dir.isEmpty() || QFileInfo(real_dir).exists()) { + QStringList files = QDir(real_dir).entryList(QStringList(wildcard)); + if (files.isEmpty()) { + q->logMessage(format("Failure to find %1").arg(val)); + } else { + QString a; + for (int i = files.count() - 1; i >= 0; --i) { + if (files[i] == QLatin1String(".") || files[i] == QLatin1String("..")) + continue; + a = dir + files[i]; + sources_out += a; + } + } + } else { + q->logMessage(format("Cannot match %1/%2, as %3 does not exist.") + .arg(real_dir).arg(wildcard).arg(real_dir)); + } + } +} + + +/* + * Lookup of files are done in this order: + * 1. look in pwd + * 2. look in vpaths + * 3. expand wild card files relative from the profiles folder + **/ + +// FIXME: This code supports something that I'd consider a flaw in .pro file syntax +// which is not even documented. So arguably this can be ditched completely... +QStringList ProFileEvaluator::Private::expandPattern(const QString& pattern) +{ + if (!currentProFile()) + return QStringList(); + + QStringList sources_out; + const QString absName = QDir::cleanPath(QDir::current().absoluteFilePath(pattern)); + + expandPatternHelper(pattern, absName, sources_out); + return sources_out; +} + +QString ProFileEvaluator::Private::format(const char *fmt) const +{ + ProFile *pro = currentProFile(); + QString fileName = pro ? pro->fileName() : QLatin1String("Not a file"); + int lineNumber = pro ? m_lineNo : 0; + return QString::fromLatin1("%1(%2):").arg(fileName).arg(lineNumber) + QString::fromAscii(fmt); +} + + +/////////////////////////////////////////////////////////////////////// +// +// ProFileEvaluator +// +/////////////////////////////////////////////////////////////////////// + +ProFileEvaluator::ProFileEvaluator() + : d(new Private(this)) +{ + Option::init(); +} + +ProFileEvaluator::~ProFileEvaluator() +{ + delete d; +} + +bool ProFileEvaluator::contains(const QString &variableName) const +{ + return d->m_valuemap.contains(variableName); +} + +QStringList ProFileEvaluator::values(const QString &variableName) const +{ + return d->values(variableName); +} + +QStringList ProFileEvaluator::values(const QString &variableName, const ProFile *pro) const +{ + return d->values(variableName, pro); +} + +ProFileEvaluator::TemplateType ProFileEvaluator::templateType() +{ + QStringList templ = values(QLatin1String("TEMPLATE")); + if (templ.count() >= 1) { + QString t = templ.last().toLower(); + if (t == QLatin1String("app")) + return TT_Application; + if (t == QLatin1String("lib")) + return TT_Library; + if (t == QLatin1String("subdirs")) + return TT_Subdirs; + } + return TT_Unknown; +} + +bool ProFileEvaluator::queryProFile(ProFile *pro) +{ + return d->read(pro); +} + +bool ProFileEvaluator::accept(ProFile *pro) +{ + return pro->Accept(d); +} + +QString ProFileEvaluator::propertyValue(const QString &name) const +{ + return d->propertyValue(name); +} + +namespace { + template<class K, class T> void insert(QHash<K,T> *out, const QHash<K,T> &in) + { + typename QHash<K,T>::const_iterator i = in.begin(); + while (i != in.end()) { + out->insert(i.key(), i.value()); + ++i; + } + } +} // anon namespace + +void ProFileEvaluator::addVariables(const QHash<QString, QStringList> &variables) +{ + insert(&(d->m_valuemap), variables); +} + +void ProFileEvaluator::addProperties(const QHash<QString, QString> &properties) +{ + insert(&(d->m_properties), properties); +} + +void ProFileEvaluator::logMessage(const QString &message) +{ + if (d->m_verbose) + qWarning("%s", qPrintable(message)); +} + +void ProFileEvaluator::fileMessage(const QString &message) +{ + qWarning("%s", qPrintable(message)); +} + +void ProFileEvaluator::errorMessage(const QString &message) +{ + qWarning("%s", qPrintable(message)); +} + +void ProFileEvaluator::setVerbose(bool on) +{ + d->m_verbose = on; +} + +void evaluateProFile(const ProFileEvaluator &visitor, QHash<QByteArray, QStringList> *varMap) +{ + QStringList sourceFiles; + QString codecForTr; + QString codecForSource; + QStringList tsFileNames; + + // app/lib template + sourceFiles += visitor.values(QLatin1String("SOURCES")); + sourceFiles += visitor.values(QLatin1String("HEADERS")); + tsFileNames = visitor.values(QLatin1String("TRANSLATIONS")); + + QStringList trcodec = visitor.values(QLatin1String("CODEC")) + + visitor.values(QLatin1String("DEFAULTCODEC")) + + visitor.values(QLatin1String("CODECFORTR")); + if (!trcodec.isEmpty()) + codecForTr = trcodec.last(); + + QStringList srccodec = visitor.values(QLatin1String("CODECFORSRC")); + if (!srccodec.isEmpty()) + codecForSource = srccodec.last(); + + QStringList forms = visitor.values(QLatin1String("INTERFACES")) + + visitor.values(QLatin1String("FORMS")) + + visitor.values(QLatin1String("FORMS3")); + sourceFiles << forms; + + sourceFiles.sort(); + sourceFiles.removeDuplicates(); + tsFileNames.sort(); + tsFileNames.removeDuplicates(); + + varMap->insert("SOURCES", sourceFiles); + varMap->insert("CODECFORTR", QStringList() << codecForTr); + varMap->insert("CODECFORSRC", QStringList() << codecForSource); + varMap->insert("TRANSLATIONS", tsFileNames); +} + +bool evaluateProFile(const QString &fileName, bool verbose, QHash<QByteArray, QStringList> *varMap) +{ + QFileInfo fi(fileName); + if (!fi.exists()) + return false; + + ProFile pro(fi.absoluteFilePath()); + + ProFileEvaluator visitor; + visitor.setVerbose(verbose); + + if (!visitor.queryProFile(&pro)) + return false; + + if (!visitor.accept(&pro)) + return false; + + evaluateProFile(visitor, varMap); + + return true; +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/profileevaluator.h b/tools/linguist/shared/profileevaluator.h new file mode 100644 index 0000000..beb16ea --- /dev/null +++ b/tools/linguist/shared/profileevaluator.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROFILEEVALUATOR_H +#define PROFILEEVALUATOR_H + +#include "proitems.h" +#include "abstractproitemvisitor.h" + +#include <QtCore/QIODevice> +#include <QtCore/QHash> +#include <QtCore/QStringList> +#include <QtCore/QStack> + +QT_BEGIN_NAMESPACE + +class ProFile; +class ProFileEvaluator; + +void evaluateProFile(const ProFileEvaluator &visitor, QHash<QByteArray, QStringList> *varMap); +bool evaluateProFile(const QString &fileName, bool verbose, QHash<QByteArray, QStringList> *varMap); + +class ProFileEvaluator +{ +public: + enum TemplateType { + TT_Unknown = 0, + TT_Application, + TT_Library, + TT_Subdirs + }; + + ProFileEvaluator(); + virtual ~ProFileEvaluator(); + + ProFileEvaluator::TemplateType templateType(); + virtual bool contains(const QString &variableName) const; + void setVerbose(bool on); + + bool queryProFile(ProFile *pro); + bool accept(ProFile *pro); + + void addVariables(const QHash<QString, QStringList> &variables); + void addProperties(const QHash<QString, QString> &properties); + QStringList values(const QString &variableName) const; + QStringList values(const QString &variableName, const ProFile *pro) const; + QString propertyValue(const QString &val) const; + + // for our descendents + virtual ProFile *parsedProFile(const QString &fileName); + virtual void releaseParsedProFile(ProFile *proFile); + virtual void logMessage(const QString &msg); + virtual void errorMessage(const QString &msg); // .pro parse errors + virtual void fileMessage(const QString &msg); // error() and message() from .pro file + +private: + class Private; + Private *d; +}; + +QT_END_NAMESPACE + +#endif // PROFILEEVALUATOR_H diff --git a/tools/linguist/shared/proitems.cpp b/tools/linguist/shared/proitems.cpp new file mode 100644 index 0000000..1895852 --- /dev/null +++ b/tools/linguist/shared/proitems.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "proitems.h" +#include "abstractproitemvisitor.h" + +#include <QtCore/QFileInfo> + +QT_BEGIN_NAMESPACE + +// --------------- ProItem ------------ +void ProItem::setComment(const QString &comment) +{ + m_comment = comment; +} + +QString ProItem::comment() const +{ + return m_comment; +} + +// --------------- ProBlock ---------------- +ProBlock::ProBlock(ProBlock *parent) +{ + m_blockKind = 0; + m_parent = parent; +} + +ProBlock::~ProBlock() +{ + qDeleteAll(m_proitems); +} + +void ProBlock::appendItem(ProItem *proitem) +{ + m_proitems << proitem; +} + +void ProBlock::setItems(const QList<ProItem *> &proitems) +{ + m_proitems = proitems; +} + +QList<ProItem *> ProBlock::items() const +{ + return m_proitems; +} + +void ProBlock::setBlockKind(int blockKind) +{ + m_blockKind = blockKind; +} + +int ProBlock::blockKind() const +{ + return m_blockKind; +} + +void ProBlock::setParent(ProBlock *parent) +{ + m_parent = parent; +} + +ProBlock *ProBlock::parent() const +{ + return m_parent; +} + +ProItem::ProItemKind ProBlock::kind() const +{ + return ProItem::BlockKind; +} + +bool ProBlock::Accept(AbstractProItemVisitor *visitor) +{ + visitor->visitBeginProBlock(this); + foreach (ProItem *item, m_proitems) { + if (!item->Accept(visitor)) + return false; + } + return visitor->visitEndProBlock(this); +} + +// --------------- ProVariable ---------------- +ProVariable::ProVariable(const QString &name, ProBlock *parent) + : ProBlock(parent) +{ + setBlockKind(ProBlock::VariableKind); + m_variable = name; + m_variableKind = SetOperator; +} + +void ProVariable::setVariableOperator(VariableOperator variableKind) +{ + m_variableKind = variableKind; +} + +ProVariable::VariableOperator ProVariable::variableOperator() const +{ + return m_variableKind; +} + +void ProVariable::setVariable(const QString &name) +{ + m_variable = name; +} + +QString ProVariable::variable() const +{ + return m_variable; +} + +bool ProVariable::Accept(AbstractProItemVisitor *visitor) +{ + visitor->visitBeginProVariable(this); + foreach (ProItem *item, m_proitems) { + if (!item->Accept(visitor)) + return false; + } + return visitor->visitEndProVariable(this); +} + +// --------------- ProValue ---------------- +ProValue::ProValue(const QString &value, ProVariable *variable) +{ + m_variable = variable; + m_value = value; +} + +void ProValue::setValue(const QString &value) +{ + m_value = value; +} + +QString ProValue::value() const +{ + return m_value; +} + +void ProValue::setVariable(ProVariable *variable) +{ + m_variable = variable; +} + +ProVariable *ProValue::variable() const +{ + return m_variable; +} + +ProItem::ProItemKind ProValue::kind() const +{ + return ProItem::ValueKind; +} + +bool ProValue::Accept(AbstractProItemVisitor *visitor) +{ + return visitor->visitProValue(this); +} + +// --------------- ProFunction ---------------- +ProFunction::ProFunction(const QString &text) +{ + m_text = text; +} + +void ProFunction::setText(const QString &text) +{ + m_text = text; +} + +QString ProFunction::text() const +{ + return m_text; +} + +ProItem::ProItemKind ProFunction::kind() const +{ + return ProItem::FunctionKind; +} + +bool ProFunction::Accept(AbstractProItemVisitor *visitor) +{ + return visitor->visitProFunction(this); +} + +// --------------- ProCondition ---------------- +ProCondition::ProCondition(const QString &text) +{ + m_text = text; +} + +void ProCondition::setText(const QString &text) +{ + m_text = text; +} + +QString ProCondition::text() const +{ + return m_text; +} + +ProItem::ProItemKind ProCondition::kind() const +{ + return ProItem::ConditionKind; +} + +bool ProCondition::Accept(AbstractProItemVisitor *visitor) +{ + return visitor->visitProCondition(this); +} + +// --------------- ProOperator ---------------- +ProOperator::ProOperator(OperatorKind operatorKind) +{ + m_operatorKind = operatorKind; +} + +void ProOperator::setOperatorKind(OperatorKind operatorKind) +{ + m_operatorKind = operatorKind; +} + +ProOperator::OperatorKind ProOperator::operatorKind() const +{ + return m_operatorKind; +} + +ProItem::ProItemKind ProOperator::kind() const +{ + return ProItem::OperatorKind; +} + +bool ProOperator::Accept(AbstractProItemVisitor *visitor) +{ + return visitor->visitProOperator(this); +} + +// --------------- ProFile ---------------- +ProFile::ProFile(const QString &fileName) + : ProBlock(0) +{ + m_modified = false; + setBlockKind(ProBlock::ProFileKind); + m_fileName = fileName; + + QFileInfo fi(fileName); + m_displayFileName = fi.fileName(); + m_directoryName = fi.absolutePath(); +} + +ProFile::~ProFile() +{ +} + +QString ProFile::displayFileName() const +{ + return m_displayFileName; +} + +QString ProFile::fileName() const +{ + return m_fileName; +} + +QString ProFile::directoryName() const +{ + return m_directoryName; +} + +void ProFile::setModified(bool modified) +{ + m_modified = modified; +} + +bool ProFile::isModified() const +{ + return m_modified; +} + +bool ProFile::Accept(AbstractProItemVisitor *visitor) +{ + visitor->visitBeginProFile(this); + foreach (ProItem *item, m_proitems) { + if (!item->Accept(visitor)) + return false; + } + return visitor->visitEndProFile(this); +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/proitems.h b/tools/linguist/shared/proitems.h new file mode 100644 index 0000000..befaa88 --- /dev/null +++ b/tools/linguist/shared/proitems.h @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROITEMS_H +#define PROITEMS_H + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE + +struct AbstractProItemVisitor; + +class ProItem +{ +public: + enum ProItemKind { + ValueKind, + FunctionKind, + ConditionKind, + OperatorKind, + BlockKind + }; + + ProItem() : m_lineNumber(0) {} + virtual ~ProItem() {} + + virtual ProItemKind kind() const = 0; + + void setComment(const QString &comment); + QString comment() const; + + virtual bool Accept(AbstractProItemVisitor *visitor) = 0; + int lineNumber() const { return m_lineNumber; } + void setLineNumber(int lineNumber) { m_lineNumber = lineNumber; } + +private: + QString m_comment; + int m_lineNumber; +}; + +class ProBlock : public ProItem +{ +public: + enum ProBlockKind { + NormalKind = 0x00, + ScopeKind = 0x01, + ScopeContentsKind = 0x02, + VariableKind = 0x04, + ProFileKind = 0x08, + SingleLine = 0x10 + }; + + ProBlock(ProBlock *parent); + ~ProBlock(); + + void appendItem(ProItem *proitem); + void setItems(const QList<ProItem *> &proitems); + QList<ProItem *> items() const; + + void setBlockKind(int blockKind); + int blockKind() const; + + void setParent(ProBlock *parent); + ProBlock *parent() const; + + ProItem::ProItemKind kind() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +protected: + QList<ProItem *> m_proitems; +private: + ProBlock *m_parent; + int m_blockKind; +}; + +class ProVariable : public ProBlock +{ +public: + enum VariableOperator { + AddOperator = 0, + RemoveOperator = 1, + ReplaceOperator = 2, + SetOperator = 3, + UniqueAddOperator = 4 + }; + + ProVariable(const QString &name, ProBlock *parent); + + void setVariableOperator(VariableOperator variableKind); + VariableOperator variableOperator() const; + + void setVariable(const QString &name); + QString variable() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +private: + VariableOperator m_variableKind; + QString m_variable; +}; + +class ProValue : public ProItem +{ +public: + ProValue(const QString &value, ProVariable *variable); + + void setValue(const QString &value); + QString value() const; + + void setVariable(ProVariable *variable); + ProVariable *variable() const; + + ProItem::ProItemKind kind() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +private: + QString m_value; + ProVariable *m_variable; +}; + +class ProFunction : public ProItem +{ +public: + explicit ProFunction(const QString &text); + + void setText(const QString &text); + QString text() const; + + ProItem::ProItemKind kind() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +private: + QString m_text; +}; + +class ProCondition : public ProItem +{ +public: + explicit ProCondition(const QString &text); + + void setText(const QString &text); + QString text() const; + + ProItem::ProItemKind kind() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +private: + QString m_text; +}; + +class ProOperator : public ProItem +{ +public: + enum OperatorKind { + OrOperator = 1, + NotOperator = 2 + }; + + explicit ProOperator(OperatorKind operatorKind); + + void setOperatorKind(OperatorKind operatorKind); + OperatorKind operatorKind() const; + + ProItem::ProItemKind kind() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); +private: + OperatorKind m_operatorKind; +}; + +class ProFile : public QObject, public ProBlock +{ + Q_OBJECT + +public: + explicit ProFile(const QString &fileName); + ~ProFile(); + + QString displayFileName() const; + QString fileName() const; + QString directoryName() const; + + void setModified(bool modified); + bool isModified() const; + + virtual bool Accept(AbstractProItemVisitor *visitor); + +private: + QString m_fileName; + QString m_displayFileName; + QString m_directoryName; + bool m_modified; +}; + +QT_END_NAMESPACE + +#endif // PROITEMS_H diff --git a/tools/linguist/shared/proparser.pri b/tools/linguist/shared/proparser.pri new file mode 100644 index 0000000..372247e --- /dev/null +++ b/tools/linguist/shared/proparser.pri @@ -0,0 +1,12 @@ + +INCLUDEPATH *= $$PWD + +HEADERS += \ + $$PWD/abstractproitemvisitor.h \ + $$PWD/proitems.h \ + $$PWD/profileevaluator.h \ + $$PWD/proparserutils.h + +SOURCES += \ + $$PWD/proitems.cpp \ + $$PWD/profileevaluator.cpp diff --git a/tools/linguist/shared/proparserutils.h b/tools/linguist/shared/proparserutils.h new file mode 100644 index 0000000..8914a8e --- /dev/null +++ b/tools/linguist/shared/proparserutils.h @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROPARSERUTILS_H +#define PROPARSERUTILS_H + +#include <QtCore/QDir> +#include <QtCore/QLibraryInfo> + +QT_BEGIN_NAMESPACE + +// Pre- and postcondition macros +#define PRE(cond) do {if (!(cond))qt_assert(#cond,__FILE__,__LINE__);} while (0) +#define POST(cond) do {if (!(cond))qt_assert(#cond,__FILE__,__LINE__);} while (0) + +// This struct is from qmake, but we are not using everything. +struct Option +{ + //simply global convenience + //static QString libtool_ext; + //static QString pkgcfg_ext; + //static QString prf_ext; + //static QString prl_ext; + //static QString ui_ext; + //static QStringList h_ext; + //static QStringList cpp_ext; + //static QString h_moc_ext; + //static QString cpp_moc_ext; + //static QString obj_ext; + //static QString lex_ext; + //static QString yacc_ext; + //static QString h_moc_mod; + //static QString cpp_moc_mod; + //static QString lex_mod; + //static QString yacc_mod; + static QString dir_sep; + static QString dirlist_sep; + static QString qmakespec; + static QChar field_sep; + + enum TARG_MODE { TARG_UNIX_MODE, TARG_WIN_MODE, TARG_MACX_MODE, TARG_MAC9_MODE, TARG_QNX6_MODE }; + static TARG_MODE target_mode; + //static QString pro_ext; + //static QString res_ext; + + static void init() + { +#ifdef Q_OS_WIN + Option::dirlist_sep = QLatin1Char(';'); + Option::dir_sep = QLatin1Char('\\'); +#else + Option::dirlist_sep = QLatin1Char(':'); + Option::dir_sep = QLatin1Char(QLatin1Char('/')); +#endif + Option::qmakespec = QString::fromLatin1(qgetenv("QMAKESPEC").data()); + Option::field_sep = QLatin1Char(' '); + } +}; +#if defined(Q_OS_WIN32) +Option::TARG_MODE Option::target_mode = Option::TARG_WIN_MODE; +#elif defined(Q_OS_MAC) +Option::TARG_MODE Option::target_mode = Option::TARG_MACX_MODE; +#elif defined(Q_OS_QNX6) +Option::TARG_MODE Option::target_mode = Option::TARG_QNX6_MODE; +#else +Option::TARG_MODE Option::target_mode = Option::TARG_UNIX_MODE; +#endif + +QString Option::qmakespec; +QString Option::dirlist_sep; +QString Option::dir_sep; +QChar Option::field_sep; + +static void insertUnique(QHash<QString, QStringList> *map, + const QString &key, const QStringList &value, bool unique = true) +{ + QStringList &sl = (*map)[key]; + if (!unique) { + sl += value; + } else { + for (int i = 0; i < value.count(); ++i) { + if (!sl.contains(value.at(i))) + sl.append(value.at(i)); + } + } +} + +/* + See ProFileEvaluator::Private::visitProValue(...) + +static QStringList replaceInList(const QStringList &varList, const QRegExp ®exp, + const QString &replace, bool global) +{ + QStringList resultList = varList; + + for (QStringList::Iterator varit = resultList.begin(); varit != resultList.end();) { + if (varit->contains(regexp)) { + *varit = varit->replace(regexp, replace); + if (varit->isEmpty()) + varit = resultList.erase(varit); + else + ++varit; + if (!global) + break; + } else { + ++varit; + } + } + return resultList; +} +*/ + +inline QStringList splitPathList(const QString paths) +{ + return paths.split(Option::dirlist_sep); +} + +static QStringList split_arg_list(QString params) +{ + int quote = 0; + QStringList args; + + const ushort LPAREN = '('; + const ushort RPAREN = ')'; + const ushort SINGLEQUOTE = '\''; + const ushort DOUBLEQUOTE = '"'; + const ushort COMMA = ','; + const ushort SPACE = ' '; + //const ushort TAB = '\t'; + + ushort unicode; + const QChar *params_data = params.data(); + const int params_len = params.length(); + int last = 0; + while (last < params_len && ((params_data+last)->unicode() == SPACE + /*|| (params_data+last)->unicode() == TAB*/)) + ++last; + for (int x = last, parens = 0; x <= params_len; x++) { + unicode = (params_data+x)->unicode(); + if (x == params_len) { + while (x && (params_data+(x-1))->unicode() == SPACE) + --x; + QString mid(params_data+last, x-last); + if (quote) { + if (mid[0] == quote && mid[(int)mid.length()-1] == quote) + mid = mid.mid(1, mid.length()-2); + quote = 0; + } + args << mid; + break; + } + if (unicode == LPAREN) { + --parens; + } else if (unicode == RPAREN) { + ++parens; + } else if (quote && unicode == quote) { + quote = 0; + } else if (!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) { + quote = unicode; + } + if (!parens && !quote && unicode == COMMA) { + QString mid = params.mid(last, x - last).trimmed(); + args << mid; + last = x+1; + while (last < params_len && ((params_data+last)->unicode() == SPACE + /*|| (params_data+last)->unicode() == TAB*/)) + ++last; + } + } + return args; +} + +static QStringList split_value_list(const QString &vals, bool do_semicolon=false) +{ + QString build; + QStringList ret; + QStack<char> quote; + + const ushort LPAREN = '('; + const ushort RPAREN = ')'; + const ushort SINGLEQUOTE = '\''; + const ushort DOUBLEQUOTE = '"'; + const ushort BACKSLASH = '\\'; + const ushort SEMICOLON = ';'; + + ushort unicode; + const QChar *vals_data = vals.data(); + const int vals_len = vals.length(); + for (int x = 0, parens = 0; x < vals_len; x++) { + unicode = vals_data[x].unicode(); + if (x != (int)vals_len-1 && unicode == BACKSLASH && + (vals_data[x+1].unicode() == SINGLEQUOTE || vals_data[x+1].unicode() == DOUBLEQUOTE)) { + build += vals_data[x++]; //get that 'escape' + } else if (!quote.isEmpty() && unicode == quote.top()) { + quote.pop(); + } else if (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE) { + quote.push(unicode); + } else if (unicode == RPAREN) { + --parens; + } else if (unicode == LPAREN) { + ++parens; + } + + if (!parens && quote.isEmpty() && ((do_semicolon && unicode == SEMICOLON) || + vals_data[x] == Option::field_sep)) { + ret << build; + build.clear(); + } else { + build += vals_data[x]; + } + } + if (!build.isEmpty()) + ret << build; + return ret; +} + +static QStringList qmake_mkspec_paths() +{ + QStringList ret; + const QString concat = QDir::separator() + QString(QLatin1String("mkspecs")); + QByteArray qmakepath = qgetenv("QMAKEPATH"); + if (!qmakepath.isEmpty()) { + const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath)); + for (QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it) + ret << ((*it) + concat); + } + ret << QLibraryInfo::location(QLibraryInfo::DataPath) + concat; + + return ret; +} + +QT_END_NAMESPACE + +#endif // PROPARSERUTILS_H diff --git a/tools/linguist/shared/qm.cpp b/tools/linguist/shared/qm.cpp new file mode 100644 index 0000000..c197e2b --- /dev/null +++ b/tools/linguist/shared/qm.cpp @@ -0,0 +1,717 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QTextCodec> + +QT_BEGIN_NAMESPACE + +// magic number for the file +static const int MagicLength = 16; +static const uchar magic[MagicLength] = { + 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95, + 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd +}; + + +namespace { + +enum Tag { + Tag_End = 1, + Tag_SourceText16 = 2, + Tag_Translation = 3, + Tag_Context16 = 4, + Tag_Obsolete1 = 5, + Tag_SourceText = 6, + Tag_Context = 7, + Tag_Comment = 8, + Tag_Obsolete2 = 9 +}; + +enum Prefix { + NoPrefix, + Hash, + HashContext, + HashContextSourceText, + HashContextSourceTextComment +}; + +} // namespace anon + +static uint elfHash(const QByteArray &ba) +{ + const uchar *k = (const uchar *)ba.data(); + uint h = 0; + uint g; + + if (k) { + while (*k) { + h = (h << 4) + *k++; + if ((g = (h & 0xf0000000)) != 0) + h ^= g >> 24; + h &= ~g; + } + } + if (!h) + h = 1; + return h; +} + +class Releaser +{ +public: + struct Offset { + Offset() + : h(0), o(0) + {} + Offset(uint hash, uint offset) + : h(hash), o(offset) + {} + + bool operator<(const Offset &other) const { + return (h != other.h) ? h < other.h : o < other.o; + } + bool operator==(const Offset &other) const { + return h == other.h && o == other.o; + } + uint h; + uint o; + }; + + enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 }; + + Releaser() : m_codec(0) {} + + void setCodecName(const QByteArray &codecName) + { + m_codec = QTextCodec::codecForName(codecName); + } + + TranslatorMessage findMessage(const QString &context, + const QString &sourceText, const QString &comment, + const QString &fileName = QString(), int lineNumber = -1) const; + + bool save(QIODevice *iod); + + void insert(const TranslatorMessage &); + void remove(const TranslatorMessage &); + + bool contains(const QString &context, const QString &sourceText, + const QString & comment) const; + + bool contains(const QString &context, const QString &comment, + const QString &fileName, int lineNumber) const; + + void squeeze(TranslatorSaveMode mode); + + QList<TranslatorMessage> messages() const; + + bool isEmpty() const; + + void setNumerusRules(const QByteArray &rules); + +private: + Q_DISABLE_COPY(Releaser) + + // This should reproduce the byte array fetched from the source file, which + // on turn should be the same as passed to the actual tr(...) calls + QByteArray originalBytes(const QString &str, bool isUtf8) const; + + Prefix commonPrefix(const TranslatorMessage &m1, const TranslatorMessage &m2) const; + + uint msgHash(const TranslatorMessage &msg) const; + + void writeMessage(const TranslatorMessage & msg, QDataStream & stream, + TranslatorSaveMode strip, Prefix prefix) const; + + // for squeezed but non-file data, this is what needs to be deleted + QByteArray m_messageArray; + QByteArray m_offsetArray; + QByteArray m_contextArray; + QMap<TranslatorMessage, void *> m_messages; + QByteArray m_numerusRules; + + // Used to reproduce the original bytes + QTextCodec *m_codec; +}; + +QByteArray Releaser::originalBytes(const QString &str, bool isUtf8) const +{ + if (str.isEmpty()) { + // Do not use QByteArray() here as the result of the serialization + // will be different. + return QByteArray(""); + } + if (isUtf8) + return str.toUtf8(); + return m_codec ? m_codec->fromUnicode(str) : str.toLatin1(); +} + +uint Releaser::msgHash(const TranslatorMessage &msg) const +{ + return elfHash(originalBytes(msg.sourceText() + msg.comment(), msg.isUtf8())); +} + +Prefix Releaser::commonPrefix(const TranslatorMessage &m1, const TranslatorMessage &m2) const +{ + if (msgHash(m1) != msgHash(m2)) + return NoPrefix; + if (m1.context() != m2.context()) + return Hash; + if (m1.sourceText() != m2.sourceText()) + return HashContext; + if (m1.comment() != m2.comment()) + return HashContextSourceText; + return HashContextSourceTextComment; +} + +void Releaser::writeMessage(const TranslatorMessage & msg, QDataStream & stream, + TranslatorSaveMode mode, Prefix prefix) const +{ + for (int i = 0; i < msg.translations().count(); ++i) { + QString str = msg.translations().at(i); + str.replace(QChar(Translator::DefaultVariantSeparator), + QChar(Translator::InternalVariantSeparator)); + stream << quint8(Tag_Translation) << str; + } + + if (mode == SaveEverything) + prefix = HashContextSourceTextComment; + + // lrelease produces "wrong" .qm files for QByteArrays that are .isNull(). + switch (prefix) { + default: + case HashContextSourceTextComment: + stream << quint8(Tag_Comment) << originalBytes(msg.comment(), msg.isUtf8()); + // fall through + case HashContextSourceText: + stream << quint8(Tag_SourceText) << originalBytes(msg.sourceText(), msg.isUtf8()); + // fall through + case HashContext: + stream << quint8(Tag_Context) << originalBytes(msg.context(), msg.isUtf8()); + ; + } + + stream << quint8(Tag_End); +} + + +bool Releaser::save(QIODevice *iod) +{ + QDataStream s(iod); + s.writeRawData((const char *)magic, MagicLength); + + if (!m_offsetArray.isEmpty()) { + quint32 oas = quint32(m_offsetArray.size()); + s << quint8(Hashes) << oas; + s.writeRawData(m_offsetArray.constData(), oas); + } + if (!m_messageArray.isEmpty()) { + quint32 mas = quint32(m_messageArray.size()); + s << quint8(Messages) << mas; + s.writeRawData(m_messageArray.constData(), mas); + } + if (!m_contextArray.isEmpty()) { + quint32 cas = quint32(m_contextArray.size()); + s << quint8(Contexts) << cas; + s.writeRawData(m_contextArray.constData(), cas); + } + if (!m_numerusRules.isEmpty()) { + quint32 nrs = m_numerusRules.size(); + s << quint8(NumerusRules) << nrs; + s.writeRawData(m_numerusRules.constData(), nrs); + } + return true; +} + +void Releaser::squeeze(TranslatorSaveMode mode) +{ + if (m_messages.isEmpty() && mode == SaveEverything) + return; + + QMap<TranslatorMessage, void *> messages = m_messages; + + // re-build contents + m_messageArray.clear(); + m_offsetArray.clear(); + m_contextArray.clear(); + m_messages.clear(); + + QMap<Offset, void *> offsets; + + QDataStream ms(&m_messageArray, QIODevice::WriteOnly); + QMap<TranslatorMessage, void *>::const_iterator it, next; + int cpPrev = 0, cpNext = 0; + for (it = messages.constBegin(); it != messages.constEnd(); ++it) { + cpPrev = cpNext; + next = it; + ++next; + if (next == messages.constEnd()) + cpNext = 0; + else + cpNext = commonPrefix(it.key(), next.key()); + offsets.insert(Offset(msgHash(it.key()), ms.device()->pos()), (void *)0); + writeMessage(it.key(), ms, mode, Prefix(qMax(cpPrev, cpNext + 1))); + } + + QMap<Offset, void *>::Iterator offset; + offset = offsets.begin(); + QDataStream ds(&m_offsetArray, QIODevice::WriteOnly); + while (offset != offsets.end()) { + Offset k = offset.key(); + ++offset; + ds << quint32(k.h) << quint32(k.o); + } + + if (mode == SaveStripped) { + QMap<QString, int> contextSet; + for (it = messages.constBegin(); it != messages.constEnd(); ++it) + ++contextSet[it.key().context()]; + + quint16 hTableSize; + if (contextSet.size() < 200) + hTableSize = (contextSet.size() < 60) ? 151 : 503; + else if (contextSet.size() < 2500) + hTableSize = (contextSet.size() < 750) ? 1511 : 5003; + else + hTableSize = (contextSet.size() < 10000) ? 15013 : 3 * contextSet.size() / 2; + + QMultiMap<int, QString> hashMap; + QMap<QString, int>::const_iterator c; + for (c = contextSet.constBegin(); c != contextSet.constEnd(); ++c) + hashMap.insert(elfHash(originalBytes(c.key(), false /*FIXME*/)) % hTableSize, c.key()); + + /* + The contexts found in this translator are stored in a hash + table to provide fast lookup. The context array has the + following format: + + quint16 hTableSize; + quint16 hTable[hTableSize]; + quint8 contextPool[...]; + + The context pool stores the contexts as Pascal strings: + + quint8 len; + quint8 data[len]; + + Let's consider the look-up of context "FunnyDialog". A + hash value between 0 and hTableSize - 1 is computed, say h. + If hTable[h] is 0, "FunnyDialog" is not covered by this + translator. Else, we check in the contextPool at offset + 2 * hTable[h] to see if "FunnyDialog" is one of the + contexts stored there, until we find it or we meet the + empty string. + */ + m_contextArray.resize(2 + (hTableSize << 1)); + QDataStream t(&m_contextArray, QIODevice::WriteOnly); + + quint16 *hTable = new quint16[hTableSize]; + memset(hTable, 0, hTableSize * sizeof(quint16)); + + t << hTableSize; + t.device()->seek(2 + (hTableSize << 1)); + t << quint16(0); // the entry at offset 0 cannot be used + uint upto = 2; + + QMap<int, QString>::const_iterator entry = hashMap.constBegin(); + while (entry != hashMap.constEnd()) { + int i = entry.key(); + hTable[i] = quint16(upto >> 1); + + do { + QString context = entry.value(); + QByteArray ba = context.toUtf8(); + const char *con = ba.data(); + uint len = uint(qstrlen(con)); + len = qMin(len, 255u); + t << quint8(len); + t.writeRawData(con, len); + upto += 1 + len; + ++entry; + } while (entry != hashMap.constEnd() && entry.key() == i); + if (upto & 0x1) { + // offsets have to be even + t << quint8(0); // empty string + ++upto; + } + } + t.device()->seek(2); + for (int j = 0; j < hTableSize; j++) + t << hTable[j]; + delete [] hTable; + + if (upto > 131072) { + qWarning("Releaser::squeeze: Too many contexts"); + m_contextArray.clear(); + } + } +} + +bool Releaser::contains(const QString &context, const QString &sourceText, + const QString &comment) const +{ + return !findMessage(context, sourceText, comment).translation().isNull(); +} + +bool Releaser::contains(const QString &context, const QString &comment, + const QString &fileName, int lineNumber) const +{ + return !findMessage(context, QString(), comment, fileName, lineNumber).isNull(); +} + +void Releaser::insert(const TranslatorMessage &message) +{ + m_messages.insert(message, 0); +} + +void Releaser::remove(const TranslatorMessage &message) +{ + m_messages.remove(message); +} + + +TranslatorMessage Releaser::findMessage(const QString &context, + const QString &sourceText, const QString &comment, + const QString &fileName, int lineNumber) const +{ + if (m_messages.isEmpty()) + return TranslatorMessage(); + + QMap<TranslatorMessage, void *>::const_iterator it; + + // Either we want to find an item that matches context, sourcetext + // (and optionally comment) Or we want to find an item that + // matches context, filename, linenumber (and optionally comment) + TranslatorMessage msg(context, sourceText, comment, QString(), fileName, lineNumber); + it = m_messages.constFind(msg); + if (it != m_messages.constEnd()) + return it.key(); + + if (!comment.isEmpty()) { + it = m_messages.constFind(TranslatorMessage(context, sourceText, QString(), QString(), fileName, lineNumber)); + if (it != m_messages.constEnd()) + return it.key(); + } + + it = m_messages.constFind(TranslatorMessage(context, QString(), comment, QString(), fileName, lineNumber)); + if (it != m_messages.constEnd()) + return it.key(); + if (comment.isEmpty()) + return TranslatorMessage(); + + it = m_messages.constFind(TranslatorMessage(context, QString(), QString(), QString(), fileName, lineNumber)); + if (it != m_messages.constEnd()) + return it.key(); + return TranslatorMessage(); +} + +bool Releaser::isEmpty() const +{ + return m_messageArray.isEmpty() && m_offsetArray.isEmpty() + && m_contextArray.isEmpty() && m_messages.isEmpty(); +} + +void Releaser::setNumerusRules(const QByteArray &rules) +{ + m_numerusRules = rules; +} + +QList<TranslatorMessage> Releaser::messages() const +{ + return m_messages.keys(); +} + +static quint8 read8(const uchar *data) +{ + return *data; +} + +static quint32 read32(const uchar *data) +{ + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); +} + + +bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QByteArray ba = dev.readAll(); + const uchar *data = (uchar*)ba.data(); + int len = ba.size(); + if (len < MagicLength || memcmp(data, magic, MagicLength) != 0) { + cd.appendError(QLatin1String("QM-Format error: magic marker missing")); + return false; + } + + enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69, NumerusRules = 0x88 }; + + // for squeezed but non-file data, this is what needs to be deleted + const uchar *messageArray = 0; + const uchar *offsetArray = 0; + const uchar *contextArray = 0; + const uchar *numerusRulesArray = 0; + uint messageLength = 0; + uint offsetLength = 0; + uint contextLength = 0; + uint numerusRulesLength = 0; + + bool ok = true; + const uchar *end = data + len; + + data += MagicLength; + + while (data < end - 4) { + quint8 tag = read8(data++); + quint32 blockLen = read32(data); + //qDebug() << "TAG:" << tag << "BLOCKLEN:" << blockLen; + data += 4; + if (!tag || !blockLen) + break; + if (data + blockLen > end) { + ok = false; + break; + } + + if (tag == Contexts) { + contextArray = data; + contextLength = blockLen; + //qDebug() << "CONTEXTS: " << contextLength << QByteArray((const char *)contextArray, contextLength).toHex(); + } else if (tag == Hashes) { + offsetArray = data; + offsetLength = blockLen; + //qDebug() << "HASHES: " << offsetLength << QByteArray((const char *)offsetArray, offsetLength).toHex(); + } else if (tag == Messages) { + messageArray = data; + messageLength = blockLen; + //qDebug() << "MESSAGES: " << messageLength << QByteArray((const char *)messageArray, messageLength).toHex(); + } else if (tag == NumerusRules) { + numerusRulesArray = data; + numerusRulesLength = blockLen; + //qDebug() << "NUMERUSRULES: " << numerusRulesLength << QByteArray((const char *)numerusRulesArray, numerusRulesLength).toHex(); + } + + data += blockLen; + } + + + size_t numItems = offsetLength / (2 * sizeof(quint32)); + //qDebug() << "NUMITEMS: " << numItems; + + TranslatorMessage msg; + + // FIXME: that's just a guess, the original locale data is lost... + QTextCodec *codec = QTextCodec::codecForLocale(); + + for (const uchar *start = offsetArray; start != offsetArray + (numItems << 3); start += 8) { + //quint32 hash = read32(start); + quint32 ro = read32(start + 4); + //qDebug() << "\nHASH:" << hash; + const uchar *m = messageArray + ro; + + for (;;) { + uchar tag = read8(m++); + //qDebug() << "Tag:" << tag << " ADDR: " << m; + switch(tag) { + case Tag_End: + goto end; + case Tag_Translation: { + int len = read32(m); + if (len % 1) { + cd.appendError(QLatin1String("QM-Format error")); + return false; + } + m += 4; + QString str = QString::fromUtf16((const ushort *)m, len/2); + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + for (int i = 0; i < str.length(); ++i) + str[i] = QChar((str.at(i).unicode() >> 8) + + ((str.at(i).unicode() << 8) & 0xff00)); + } + str.replace(QChar(Translator::InternalVariantSeparator), + QChar(Translator::DefaultVariantSeparator)); + msg.appendTranslation(str); + m += len; + break; + } + case Tag_Obsolete1: + m += 4; + //qDebug() << "OBSOLETE"; + break; + case Tag_SourceText: { + quint32 len = read32(m); + m += 4; + //qDebug() << "SOURCE LEN: " << len; + //qDebug() << "SOURCE: " << QByteArray((const char*)m, len); + msg.setSourceText(codec->toUnicode(QByteArray((const char*)m, len))); + m += len; + break; + } + case Tag_Context: { + quint32 len = read32(m); + m += 4; + //qDebug() << "CONTEXT LEN: " << len; + //qDebug() << "CONTEXT: " << QByteArray((const char*)m, len); + msg.setContext(codec->toUnicode(QByteArray((const char*)m, len))); + m += len; + break; + } + case Tag_Comment: { + quint32 len = read32(m); + m += 4; + //qDebug() << "COMMENT LEN: " << len; + //qDebug() << "COMMENT: " << QByteArray((const char*)m, len); + msg.setComment(codec->toUnicode(QByteArray((const char*)m, len))); + m += len; + break; + } + default: + //qDebug() << "UNKNOWN TAG" << tag; + break; + } + } + end:; + msg.setType(TranslatorMessage::Finished); + translator.append(msg); + //qDebug() << "\nHASH:" << hash << msg.sourceText() << msg.context(); + msg.setTranslations(QStringList()); + } + return ok; +} + + + +static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + Releaser releaser; + QLocale::Language l; + QLocale::Country c; + Translator::languageAndCountry(translator.languageCode(), &l, &c); + QByteArray rules; + if (getNumerusInfo(l, c, &rules, 0)) + releaser.setNumerusRules(rules); + releaser.setCodecName(translator.codecName()); + + int finished = 0; + int unfinished = 0; + int untranslated = 0; + + for (int i = 0; i != translator.messageCount(); ++i) { + const TranslatorMessage &msg = translator.message(i); + TranslatorMessage::Type typ = msg.type(); + if (typ != TranslatorMessage::Obsolete) { + if (typ == TranslatorMessage::Unfinished) { + if (msg.translation().isEmpty()) + ++untranslated; + else + ++unfinished; + } else { + ++finished; + } + QString context = msg.context(); + QString sourceText = msg.sourceText(); + QString comment = msg.comment(); + QStringList translations = msg.translations(); + + if (!cd.ignoreUnfinished() || typ != TranslatorMessage::Unfinished) { + /* + Drop the comment in (context, sourceText, comment), + unless the context is empty, + unless (context, sourceText, "") already exists or + unless we already dropped the comment of (context, + sourceText, comment0). + */ + if (comment.isEmpty() + || context.isEmpty() + || translator.contains(context, sourceText, QString()) + || !releaser.findMessage(context, sourceText, QString()).translation() + .isNull() ) { + releaser.insert(msg); + } else { + TranslatorMessage tm(context, sourceText, QString(), + QString(), QString(), -1, translations); + //filename and lineNumbers will be ignored from now. + releaser.insert(tm); + } + } + } + } + + releaser.squeeze(cd.m_saveMode); + bool saved = releaser.save(&dev); + if (saved && cd.isVerbose()) { + int generatedCount = finished + unfinished; + cd.appendError(QCoreApplication::translate("LRelease", + " Generated %n translation(s) (%1 finished and %2 unfinished)\n", 0, + QCoreApplication::CodecForTr, generatedCount).arg(finished).arg(unfinished)); + if (untranslated) + cd.appendError(QCoreApplication::translate("LRelease", + " Ignored %n untranslated source text(s)\n", 0, + QCoreApplication::CodecForTr, untranslated)); + } + return saved; +} + +int initQM() +{ + Translator::FileFormat format; + + format.extension = QLatin1String("qm"); + format.description = QObject::tr("Compiled Qt translations"); + format.fileType = Translator::FileFormat::TranslationBinary; + format.priority = 0; + format.loader = &loadQM; + format.saver = &saveQM; + Translator::registerFileFormat(format); + + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initQM) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/qph.cpp b/tools/linguist/shared/qph.cpp new file mode 100644 index 0000000..45d3a20 --- /dev/null +++ b/tools/linguist/shared/qph.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <QtXml/QXmlStreamReader> +#include <QtXml/QXmlStreamAttribute> + +QT_BEGIN_NAMESPACE + +class QPHReader : public QXmlStreamReader +{ +public: + QPHReader(QIODevice &dev, ConversionData &cd) + : QXmlStreamReader(&dev), m_cd(cd) + {} + + // the "real thing" + bool read(Translator &translator); + +private: + bool elementStarts(const QString &str) const + { + return isStartElement() && name() == str; + } + + bool isWhiteSpace() const + { + return isCharacters() && text().toString().trimmed().isEmpty(); + } + + // needed to expand <byte ... /> + QString readContents(); + // needed to join <lengthvariant>s + QString readTransContents(); + + void handleError(); + + ConversionData &m_cd; + + enum DataField { NoField, SourceField, TargetField, DefinitionField }; + DataField m_currentField; + QString m_currentSource; + QString m_currentTarget; + QString m_currentDefinition; +}; + +bool QPHReader::read(Translator &translator) +{ + m_currentField = NoField; + QString result; + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("source")) + m_currentField = SourceField; + else if (name() == QLatin1String("target")) + m_currentField = TargetField; + else if (name() == QLatin1String("definition")) + m_currentField = DefinitionField; + else + m_currentField = NoField; + } else if (isWhiteSpace()) { + // ignore these + } else if (isCharacters()) { + if (m_currentField == SourceField) + m_currentSource += text(); + else if (m_currentField == TargetField) + m_currentTarget += text(); + else if (m_currentField == DefinitionField) + m_currentDefinition += text(); + } else if (isEndElement() && name() == QLatin1String("phrase")) { + TranslatorMessage msg; + msg.setSourceText(m_currentSource); + msg.setTranslation(m_currentTarget); + msg.setTranslatorComment(m_currentDefinition); + translator.append(msg); + m_currentSource.clear(); + m_currentTarget.clear(); + m_currentDefinition.clear(); + } + } + return true; +} + +static bool loadQPH(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + translator.setLocationsType(Translator::NoLocations); + QPHReader reader(dev, cd); + return reader.read(translator); +} + +static bool saveQPH(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QTextStream t(&dev); + t << "<!DOCTYPE QPH><QPH>\n"; + foreach (const TranslatorMessage &msg, translator.messages()) { + t << "<phrase>\n"; + t << " <source>" << msg.sourceText() << "</source>\n"; + t << " <target>" << msg.translations().join(QLatin1String("@")) + << "</target>\n"; + if (!msg.context().isEmpty() || !msg.comment().isEmpty()) + t << " <definition>" << msg.context() << msg.comment() + << "</definition>\n"; + t << "</phrase>\n"; + } + t << "</QPH>\n"; + return true; +} + +int initQPH() +{ + Translator::FileFormat format; + + format.extension = QLatin1String("qph"); + format.description = QObject::tr("Qt Linguist 'Phrase Book'"); + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = 0; + format.loader = &loadQPH; + format.saver = &saveQPH; + Translator::registerFileFormat(format); + + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initQPH) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/qscript.cpp b/tools/linguist/shared/qscript.cpp new file mode 100644 index 0000000..9ab5e16 --- /dev/null +++ b/tools/linguist/shared/qscript.cpp @@ -0,0 +1,2408 @@ +// This file was generated by qlalr - DO NOT EDIT! +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +class QScriptGrammar +{ +public: + enum { + EOF_SYMBOL = 0, + T_AND = 1, + T_AND_AND = 2, + T_AND_EQ = 3, + T_AUTOMATIC_SEMICOLON = 62, + T_BREAK = 4, + T_CASE = 5, + T_CATCH = 6, + T_COLON = 7, + T_COMMA = 8, + T_CONST = 81, + T_CONTINUE = 9, + T_DEBUGGER = 82, + T_DEFAULT = 10, + T_DELETE = 11, + T_DIVIDE_ = 12, + T_DIVIDE_EQ = 13, + T_DO = 14, + T_DOT = 15, + T_ELSE = 16, + T_EQ = 17, + T_EQ_EQ = 18, + T_EQ_EQ_EQ = 19, + T_FALSE = 80, + T_FINALLY = 20, + T_FOR = 21, + T_FUNCTION = 22, + T_GE = 23, + T_GT = 24, + T_GT_GT = 25, + T_GT_GT_EQ = 26, + T_GT_GT_GT = 27, + T_GT_GT_GT_EQ = 28, + T_IDENTIFIER = 29, + T_IF = 30, + T_IN = 31, + T_INSTANCEOF = 32, + T_LBRACE = 33, + T_LBRACKET = 34, + T_LE = 35, + T_LPAREN = 36, + T_LT = 37, + T_LT_LT = 38, + T_LT_LT_EQ = 39, + T_MINUS = 40, + T_MINUS_EQ = 41, + T_MINUS_MINUS = 42, + T_NEW = 43, + T_NOT = 44, + T_NOT_EQ = 45, + T_NOT_EQ_EQ = 46, + T_NULL = 78, + T_NUMERIC_LITERAL = 47, + T_OR = 48, + T_OR_EQ = 49, + T_OR_OR = 50, + T_PLUS = 51, + T_PLUS_EQ = 52, + T_PLUS_PLUS = 53, + T_QUESTION = 54, + T_RBRACE = 55, + T_RBRACKET = 56, + T_REMAINDER = 57, + T_REMAINDER_EQ = 58, + T_RESERVED_WORD = 83, + T_RETURN = 59, + T_RPAREN = 60, + T_SEMICOLON = 61, + T_STAR = 63, + T_STAR_EQ = 64, + T_STRING_LITERAL = 65, + T_SWITCH = 66, + T_THIS = 67, + T_THROW = 68, + T_TILDE = 69, + T_TRUE = 79, + T_TRY = 70, + T_TYPEOF = 71, + T_VAR = 72, + T_VOID = 73, + T_WHILE = 74, + T_WITH = 75, + T_XOR = 76, + T_XOR_EQ = 77, + + ACCEPT_STATE = 236, + RULE_COUNT = 267, + STATE_COUNT = 465, + TERMINAL_COUNT = 84, + NON_TERMINAL_COUNT = 88, + + GOTO_INDEX_OFFSET = 465, + GOTO_INFO_OFFSET = 1374, + GOTO_CHECK_OFFSET = 1374 + }; + + static const char *const spell []; + static const int lhs []; + static const int rhs []; + static const int goto_default []; + static const int action_default []; + static const int action_index []; + static const int action_info []; + static const int action_check []; + + static inline int nt_action (int state, int nt) + { + const int *const goto_index = &action_index [GOTO_INDEX_OFFSET]; + const int *const goto_check = &action_check [GOTO_CHECK_OFFSET]; + + const int yyn = goto_index [state] + nt; + + if (yyn < 0 || goto_check [yyn] != nt) + return goto_default [nt]; + + const int *const goto_info = &action_info [GOTO_INFO_OFFSET]; + return goto_info [yyn]; + } + + static inline int t_action (int state, int token) + { + const int yyn = action_index [state] + token; + + if (yyn < 0 || action_check [yyn] != token) + return - action_default [state]; + + return action_info [yyn]; + } +}; + + +const char *const QScriptGrammar::spell [] = { + "end of file", "&", "&&", "&=", "break", "case", "catch", ":", ";", "continue", + "default", "delete", "/", "/=", "do", ".", "else", "=", "==", "===", + "finally", "for", "function", ">=", ">", ">>", ">>=", ">>>", ">>>=", "identifier", + "if", "in", "instanceof", "{", "[", "<=", "(", "<", "<<", "<<=", + "-", "-=", "--", "new", "!", "!=", "!==", "numeric literal", "|", "|=", + "||", "+", "+=", "++", "?", "}", "]", "%", "%=", "return", + ")", ";", 0, "*", "*=", "string literal", "switch", "this", "throw", "~", + "try", "typeof", "var", "void", "while", "with", "^", "^=", "null", "true", + "false", "const", "debugger", "reserved word"}; + +const int QScriptGrammar::lhs [] = { + 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 87, 87, 91, 91, 86, 86, + 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, + 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, + 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, + 94, 94, 94, 94, 94, 94, 94, 95, 95, 96, + 96, 96, 96, 96, 99, 99, 100, 100, 100, 100, + 98, 98, 101, 101, 102, 102, 103, 103, 103, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 105, + 105, 105, 105, 106, 106, 106, 107, 107, 107, 107, + 108, 108, 108, 108, 108, 108, 108, 109, 109, 109, + 109, 109, 109, 110, 110, 110, 110, 110, 111, 111, + 111, 111, 111, 112, 112, 113, 113, 114, 114, 115, + 115, 116, 116, 117, 117, 118, 118, 119, 119, 120, + 120, 121, 121, 122, 122, 123, 123, 90, 90, 124, + 124, 125, 125, 125, 125, 125, 125, 125, 125, 125, + 125, 125, 125, 89, 89, 126, 126, 127, 127, 128, + 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 130, 146, 146, 145, + 145, 131, 131, 147, 147, 148, 148, 150, 150, 149, + 151, 154, 152, 152, 155, 153, 153, 132, 133, 133, + 134, 134, 135, 135, 135, 135, 135, 135, 135, 136, + 136, 136, 136, 137, 137, 137, 137, 138, 138, 139, + 141, 156, 156, 159, 159, 157, 157, 160, 158, 140, + 142, 142, 143, 143, 143, 161, 162, 144, 163, 97, + 167, 167, 164, 164, 165, 165, 168, 84, 169, 169, + 170, 170, 166, 166, 88, 88, 171}; + +const int QScriptGrammar:: rhs[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 5, 3, 3, 2, 4, 1, 2, 0, 1, + 3, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 4, 3, 3, 1, 2, 2, 2, 4, 3, + 2, 3, 1, 3, 1, 1, 1, 2, 2, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, + 3, 3, 3, 1, 3, 3, 1, 3, 3, 3, + 1, 3, 3, 3, 3, 3, 3, 1, 3, 3, + 3, 3, 3, 1, 3, 3, 3, 3, 1, 3, + 3, 3, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 5, 1, 5, 1, 3, 1, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 0, 1, 1, 3, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 1, 2, 0, + 1, 3, 3, 1, 1, 1, 3, 1, 3, 2, + 2, 2, 0, 1, 2, 0, 1, 1, 2, 2, + 7, 5, 7, 7, 5, 9, 10, 7, 8, 2, + 2, 3, 3, 2, 2, 3, 3, 3, 3, 5, + 5, 3, 5, 1, 2, 0, 1, 4, 3, 3, + 3, 3, 3, 3, 4, 5, 2, 1, 8, 8, + 1, 3, 0, 1, 0, 1, 1, 1, 1, 2, + 1, 1, 0, 1, 0, 1, 2}; + +const int QScriptGrammar::action_default [] = { + 0, 97, 164, 128, 136, 132, 172, 179, 76, 148, + 178, 186, 174, 124, 0, 175, 262, 61, 176, 177, + 182, 77, 140, 144, 65, 94, 75, 80, 60, 0, + 114, 180, 101, 259, 258, 261, 183, 0, 194, 0, + 248, 0, 8, 9, 0, 5, 0, 263, 2, 0, + 265, 19, 0, 0, 0, 0, 0, 3, 6, 0, + 0, 166, 208, 7, 0, 1, 0, 0, 4, 0, + 0, 195, 0, 0, 0, 184, 185, 90, 0, 173, + 181, 0, 0, 77, 96, 263, 2, 265, 79, 78, + 0, 0, 0, 92, 93, 91, 0, 264, 253, 254, + 0, 251, 0, 252, 0, 255, 256, 0, 257, 250, + 260, 0, 266, 0, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 23, + 41, 42, 43, 44, 45, 25, 46, 47, 24, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, + 21, 0, 0, 0, 22, 13, 95, 0, 125, 0, + 0, 0, 0, 115, 0, 0, 0, 0, 0, 0, + 105, 0, 0, 0, 99, 100, 98, 103, 107, 106, + 104, 102, 117, 116, 118, 0, 133, 0, 129, 68, + 0, 0, 0, 70, 59, 58, 0, 0, 69, 165, + 0, 73, 71, 0, 72, 74, 209, 210, 0, 161, + 154, 152, 159, 160, 158, 157, 163, 156, 155, 153, + 162, 149, 0, 137, 0, 0, 141, 0, 0, 145, + 67, 0, 0, 63, 0, 62, 267, 224, 0, 225, + 226, 227, 220, 0, 221, 222, 223, 81, 0, 0, + 0, 0, 0, 213, 214, 170, 168, 130, 138, 134, + 150, 126, 171, 0, 77, 142, 146, 119, 108, 0, + 0, 127, 0, 0, 0, 0, 120, 0, 0, 0, + 0, 0, 112, 110, 113, 111, 109, 122, 121, 123, + 0, 135, 0, 131, 0, 169, 77, 0, 151, 166, + 167, 0, 166, 0, 0, 216, 0, 0, 0, 218, + 0, 139, 0, 0, 143, 0, 0, 147, 206, 0, + 198, 207, 201, 0, 205, 0, 166, 199, 0, 166, + 0, 0, 217, 0, 0, 0, 219, 264, 253, 0, + 0, 255, 0, 249, 0, 240, 0, 0, 0, 212, + 0, 211, 188, 191, 0, 27, 30, 31, 248, 34, + 35, 5, 39, 40, 2, 41, 44, 3, 6, 166, + 7, 48, 1, 50, 4, 52, 53, 54, 55, 56, + 57, 189, 187, 65, 66, 64, 0, 228, 229, 0, + 0, 0, 231, 236, 234, 237, 0, 0, 235, 236, + 0, 232, 0, 233, 190, 239, 0, 190, 238, 0, + 241, 242, 0, 190, 243, 244, 0, 0, 245, 0, + 0, 0, 246, 247, 83, 82, 0, 0, 0, 215, + 0, 0, 0, 230, 0, 20, 0, 17, 19, 11, + 0, 16, 12, 18, 15, 10, 0, 14, 87, 85, + 89, 86, 84, 88, 203, 196, 0, 204, 200, 0, + 202, 192, 0, 193, 197}; + +const int QScriptGrammar::goto_default [] = { + 29, 28, 436, 434, 113, 14, 2, 435, 112, 111, + 114, 193, 24, 17, 189, 26, 8, 200, 21, 27, + 77, 25, 1, 32, 30, 267, 13, 261, 3, 257, + 5, 259, 4, 258, 22, 265, 23, 266, 9, 260, + 256, 297, 386, 262, 263, 35, 6, 79, 12, 15, + 18, 19, 10, 7, 31, 80, 20, 36, 75, 76, + 11, 354, 353, 78, 456, 455, 319, 320, 458, 322, + 457, 321, 392, 396, 399, 395, 394, 414, 415, 16, + 100, 107, 96, 99, 106, 108, 33, 0}; + +const int QScriptGrammar::action_index [] = { + 1210, 59, -84, 71, 41, -1, -84, -84, 148, -84, + -84, -84, -84, 201, 130, -84, -84, -84, -84, -84, + -84, 343, 67, 62, 122, 109, -84, -84, -84, 85, + 273, -84, 184, -84, 1210, -84, -84, 119, -84, 112, + -84, 521, -84, -84, 1130, -84, 45, 54, 58, 38, + 1290, 50, 521, 521, 521, 376, 521, -84, -84, 521, + 521, 521, -84, -84, 25, -84, 521, 521, -84, 43, + 521, -84, 521, 18, 15, -84, -84, -84, 24, -84, + -84, 521, 521, 64, 153, 27, -84, 1050, -84, -84, + 521, 521, 521, -84, -84, -84, 28, -84, 37, 55, + 19, -84, 33, -84, 34, 1210, -84, 16, 1210, -84, + -84, 39, 52, -3, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, 521, + -84, 1050, 125, 521, -84, -84, 155, 521, 189, 521, + 521, 521, 521, 248, 521, 521, 521, 521, 521, 521, + 243, 521, 521, 521, 75, 82, 94, 177, 184, 184, + 184, 184, 263, 283, 298, 521, 44, 521, 77, -84, + 970, 521, 817, -84, -84, -84, 95, 521, -84, -84, + 93, -84, -84, 521, -84, -84, -84, -84, 521, -84, + -84, -84, -84, -84, -84, -84, -84, -84, -84, -84, + -84, -84, 521, 41, 521, 521, 68, 66, 521, -84, + -84, 970, 521, -84, 103, -84, -84, -84, 63, -84, + -84, -84, -84, 69, -84, -84, -84, -84, -27, 12, + 521, 92, 100, -84, -84, 890, -84, 31, -13, -45, + -84, 210, 32, -28, 387, 20, 73, 304, 117, -5, + 521, 212, 521, 521, 521, 521, 213, 521, 521, 521, + 521, 521, 151, 150, 176, 158, 168, 304, 304, 228, + 521, -72, 521, 4, 521, -84, 306, 521, -84, 521, + 8, -50, 521, -48, 1130, -84, 521, 80, 1130, -84, + 521, -33, 521, 521, 5, 48, 521, -84, 17, 88, + 11, -84, -84, 521, -84, -29, 521, -84, -41, 521, + -39, 1130, -84, 521, 87, 1130, -84, -8, -2, -35, + 10, 1210, -16, -84, 1130, -84, 521, 86, 1130, -14, + 1130, -84, -84, 1130, -36, 107, -21, 165, 3, 521, + 1130, 6, 14, 61, 7, -19, 448, -4, -6, 671, + 29, 13, 23, 521, 30, -10, 521, 9, 521, -30, + -18, -84, -84, 164, -84, -84, 46, -84, -84, 521, + 111, -24, -84, 36, -84, 40, 99, 521, -84, 21, + 22, -84, -11, -84, 1130, -84, 106, 1130, -84, 178, + -84, -84, 98, 1130, 57, -84, 56, 60, -84, 51, + 26, 35, -84, -84, -84, -84, 521, 97, 1130, -84, + 521, 90, 1130, -84, 79, 76, 744, -84, 49, -84, + 594, -84, -84, -84, -84, -84, 83, -84, -84, -84, + -84, -84, -84, -84, 42, -84, 162, -84, -84, 521, + -84, -84, 53, -84, -84, + + -61, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -4, -88, -88, 22, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -51, -88, -88, -88, -88, -88, + -88, 105, -88, -88, -12, -88, -88, -88, -88, -88, + -7, -88, 35, 132, 62, 154, 79, -88, -88, 100, + 75, 36, -88, -88, -88, -88, 37, 70, -88, -1, + 86, -88, 92, -88, -88, -88, -88, -88, -88, -88, + -88, 90, 95, -88, -88, -88, -88, -88, -88, -88, + 87, 82, 74, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -47, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, 28, + -88, 20, -88, 19, -88, -88, -88, 39, -88, 42, + 43, 106, 61, -88, 63, 55, 52, 53, 91, 125, + -88, 120, 123, 118, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, 116, -88, 59, -88, -88, + 16, 18, 15, -88, -88, -88, -88, 21, -88, -88, + -88, -88, -88, 24, -88, -88, -88, -88, 38, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, 97, -88, 115, 25, -88, -88, 26, -88, + -88, 111, 14, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + 23, -88, -88, -88, -88, 108, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, -88, + 160, -88, 171, 163, 145, 179, -88, 135, 45, 41, + 66, 80, -88, -88, -88, -88, -88, -88, -88, -88, + 172, -88, 156, -88, 142, -88, -88, 144, -88, 122, + -88, -88, 114, -88, -23, -88, 48, -88, 29, -88, + 224, -88, 157, 175, -88, -88, 182, -88, -88, -88, + -88, -88, -88, 183, -88, -21, 134, -88, -88, 49, + -88, 3, -88, 44, -88, 2, -88, -88, -37, -88, + -88, -31, -88, -88, 10, -88, 47, -88, 17, -88, + 27, -88, -88, 13, -88, -88, -88, -88, -88, 117, + 6, -88, -88, -88, -88, -88, 154, -88, -88, 1, + -88, -88, -88, 7, -88, -35, 137, -88, 141, -88, + -88, -88, -88, -6, -88, -88, -88, -88, -88, 78, + -88, -88, -88, -88, -88, -69, -88, 11, -88, -59, + -88, -88, -88, -88, 83, -88, -88, 56, -88, -88, + -88, -88, -88, -40, -58, -88, -88, -29, -88, -88, + -88, -45, -88, -88, -88, -88, -3, -88, -42, -88, + -5, -88, -32, -88, -88, -88, 9, -88, 8, -88, + -2, -88, -88, -88, -88, -88, -88, -88, -88, -88, + -88, -88, -88, -88, -88, -88, -88, -88, -88, 12, + -88, -88, -56, -88, -88}; + +const int QScriptGrammar::action_info [] = { + 318, -25, 350, -45, 292, 270, 426, 310, -194, 393, + -32, 302, 304, -37, 344, 290, 197, 346, 430, 382, + 329, 331, 310, 413, 318, 340, 397, 101, 338, 404, + -49, 292, 270, 299, 323, 290, -24, -51, -195, 343, + 294, 397, 333, 341, 403, 397, 149, 249, 250, 389, + 255, 430, 155, 454, 426, 316, 97, 437, 437, 459, + 151, 389, 103, 102, 98, 344, 101, 105, 413, 222, + 222, 109, 157, 228, 346, 187, 413, 417, 157, 104, + 420, 255, 454, 337, 443, 236, 421, 438, 197, 185, + 97, 197, 419, 413, 197, 197, 325, -263, 197, 81, + 197, 203, 0, 197, 416, 197, 88, 388, 387, 400, + 82, 197, 224, 407, 197, 81, 225, 89, 417, 197, + 187, 90, 81, 312, 241, 240, 82, 313, 0, 0, + 246, 245, 153, 82, 81, 439, 238, 231, 197, 0, + 308, 243, 171, 447, 172, 82, 348, 335, 238, 326, + 432, 198, 252, 204, 401, 173, 232, 428, 192, 235, + 0, 254, 253, 190, 0, 90, 91, 90, 239, 237, + 462, 391, 92, 244, 242, 171, 171, 172, 172, 231, + 239, 237, 191, 171, 192, 172, 197, 0, 173, 173, + 0, 207, 206, 171, 243, 172, 173, 0, 232, 0, + 192, 171, 171, 172, 172, 0, 173, 159, 160, 171, + 91, 172, 91, 0, 173, 173, 92, 0, 92, 159, + 160, 0, 173, 463, 461, 0, 244, 242, 272, 273, + 272, 273, 0, 0, 161, 162, 277, 278, 0, 411, + 410, 0, 0, 0, 0, 279, 161, 162, 280, 0, + 281, 277, 278, 0, 0, 274, 275, 274, 275, 0, + 279, 0, 0, 280, 0, 281, 0, 0, 171, 0, + 172, 164, 165, 0, 0, 0, 0, 0, 0, 166, + 167, 173, 0, 168, 0, 169, 164, 165, 0, 0, + 0, 0, 0, 0, 166, 167, 164, 165, 168, 0, + 169, 0, 0, 0, 166, 167, 164, 165, 168, 209, + 169, 0, 0, 0, 166, 167, 0, 0, 168, 210, + 169, 164, 165, 211, 0, 0, 0, 277, 278, 166, + 167, 0, 212, 168, 213, 169, 279, 0, 0, 280, + 0, 281, 0, 0, 0, 214, 209, 215, 88, 0, + 0, 0, 0, 0, 0, 216, 210, 0, 217, 89, + 211, 0, 0, 0, 218, 0, 0, 0, 0, 212, + 219, 213, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 214, 220, 215, 88, 0, 0, 42, 43, + 209, 0, 216, 0, 0, 217, 89, 0, 85, 0, + 210, 218, 0, 0, 211, 86, 0, 219, 0, 87, + 51, 0, 52, 212, 0, 213, 0, 0, 306, 55, + 220, 0, 0, 58, 0, 0, 214, 0, 215, 88, + 0, 0, 0, 0, 0, 0, 216, 0, 0, 217, + 89, 63, 0, 65, 0, 218, 0, 0, 0, 0, + 0, 219, 0, 0, 57, 68, 45, 0, 0, 0, + 42, 43, 0, 0, 220, 0, 0, 0, 0, 0, + 85, 0, 0, 0, 0, 0, 0, 86, 0, 0, + 0, 87, 51, 0, 52, 0, 0, 0, 0, 0, + 0, 55, 0, 0, 0, 58, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 63, 0, 65, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 57, 68, 45, 0, + 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, + 0, 0, 0, 85, 0, 0, 0, 0, 0, 0, + 86, 0, 0, 0, 87, 51, 0, 52, 0, 0, + 0, 53, 0, 54, 55, 56, 0, 0, 58, 0, + 0, 0, 59, 0, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 63, 0, 65, 0, + 67, 0, 70, 0, 72, 0, 0, 0, 0, 57, + 68, 45, 0, 0, 0, 41, 42, 43, 0, 0, + 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, + 0, 0, 0, 86, 0, 0, 0, 87, 51, 0, + 52, 0, 0, 0, 53, 0, 54, 55, 56, 0, + 0, 58, 0, 0, 0, 59, 0, 60, 0, 0, + 442, 0, 0, 0, 0, 0, 0, 0, 0, 63, + 0, 65, 0, 67, 0, 70, 0, 72, 0, 0, + 0, 0, 57, 68, 45, 0, 0, 0, -47, 0, + 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, + 0, 0, 0, 85, 0, 0, 0, 0, 0, 0, + 86, 0, 0, 0, 87, 51, 0, 52, 0, 0, + 0, 53, 0, 54, 55, 56, 0, 0, 58, 0, + 0, 0, 59, 0, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 63, 0, 65, 0, + 67, 0, 70, 0, 72, 0, 0, 0, 0, 57, + 68, 45, 0, 0, 0, 41, 42, 43, 0, 0, + 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, + 0, 0, 0, 86, 0, 0, 0, 87, 51, 0, + 52, 0, 0, 0, 53, 0, 54, 55, 56, 0, + 0, 58, 0, 0, 0, 59, 0, 60, 0, 0, + 445, 0, 0, 0, 0, 0, 0, 0, 0, 63, + 0, 65, 0, 67, 0, 70, 0, 72, 0, 0, + 0, 0, 57, 68, 45, 0, 0, 0, 41, 42, + 43, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 0, 0, 0, 0, 0, 0, 86, 0, 0, 0, + 87, 51, 0, 52, 0, 0, 0, 53, 0, 54, + 55, 56, 0, 0, 58, 0, 0, 0, 59, 0, + 60, 0, 0, 0, 0, 0, 0, 202, 0, 0, + 0, 0, 63, 0, 65, 0, 67, 0, 70, 0, + 72, 0, 0, 0, 0, 57, 68, 45, 0, 0, + 0, 41, 42, 43, 0, 0, 0, 0, 0, 0, + 0, 0, 85, 0, 0, 0, 0, 0, 0, 86, + 0, 0, 0, 87, 51, 0, 52, 0, 0, 0, + 53, 0, 54, 55, 56, 0, 0, 58, 0, 0, + 0, 59, 0, 60, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 63, 0, 65, 0, 67, + 0, 70, 269, 72, 0, 0, 0, 0, 57, 68, + 45, 0, 0, 0, 115, 116, 117, 0, 0, 119, + 121, 122, 0, 0, 123, 0, 124, 0, 0, 0, + 126, 127, 128, 0, 0, 0, 0, 0, 0, 195, + 130, 131, 132, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, + 0, 0, 0, 0, 0, 0, 139, 140, 141, 0, + 143, 144, 145, 146, 147, 148, 0, 0, 134, 142, + 125, 118, 120, 136, 115, 116, 117, 0, 0, 119, + 121, 122, 0, 0, 123, 0, 124, 0, 0, 0, + 126, 127, 128, 0, 0, 0, 0, 0, 0, 129, + 130, 131, 132, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 133, 0, 0, 0, 135, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, + 0, 0, 0, 0, 0, 138, 139, 140, 141, 0, + 143, 144, 145, 146, 147, 148, 0, 0, 134, 142, + 125, 118, 120, 136, 37, 0, 0, 0, 0, 39, + 0, 41, 42, 43, 44, 0, 0, 0, 0, 0, + 0, 46, 85, 0, 0, 0, 0, 0, 0, 48, + 49, 0, 0, 50, 51, 0, 52, 0, 0, 0, + 53, 0, 54, 55, 56, 0, 0, 58, 0, 0, + 0, 59, 0, 60, 0, 0, 0, 0, 0, 61, + 0, 62, 0, 0, 0, 63, 64, 65, 66, 67, + 69, 70, 71, 72, 73, 74, 0, 0, 57, 68, + 45, 38, 40, 0, 37, 0, 0, 0, 0, 39, + 0, 41, 42, 43, 44, 0, 0, 0, 0, 0, + 0, 46, 47, 0, 0, 0, 0, 0, 0, 48, + 49, 0, 0, 50, 51, 0, 52, 0, 0, 0, + 53, 0, 54, 55, 56, 0, 0, 58, 0, 0, + 0, 59, 0, 60, 0, 0, 0, 0, 0, 61, + 0, 62, 0, 0, 0, 63, 64, 65, 66, 67, + 69, 70, 71, 72, 73, 74, 0, 0, 57, 68, + 45, 38, 40, 0, 355, 116, 117, 0, 0, 357, + 121, 359, 42, 43, 360, 0, 124, 0, 0, 0, + 126, 362, 363, 0, 0, 0, 0, 0, 0, 364, + 365, 131, 132, 50, 51, 0, 52, 0, 0, 0, + 53, 0, 54, 366, 56, 0, 0, 368, 0, 0, + 0, 59, 0, 60, 0, -190, 0, 0, 0, 369, + 0, 62, 0, 0, 0, 370, 371, 372, 373, 67, + 375, 376, 377, 378, 379, 380, 0, 0, 367, 374, + 361, 356, 358, 136, + + 431, 422, 427, 429, 441, 352, 300, 398, 385, 464, + 440, 412, 409, 433, 402, 444, 406, 423, 460, 234, + 418, 201, 305, 196, 34, 154, 194, 199, 251, 152, + 205, 227, 229, 248, 150, 110, 230, 208, 352, 110, + 446, 300, 409, 339, 221, 412, 327, 336, 332, 334, + 342, 248, 347, 307, 300, 345, 0, 83, 381, 83, + 83, 83, 349, 83, 284, 158, 163, 182, 283, 0, + 83, 83, 351, 83, 309, 178, 179, 83, 177, 83, + 83, 83, 449, 390, 83, 184, 170, 188, 83, 285, + 453, 330, 83, 83, 95, 452, 0, 83, 83, 450, + 83, 352, 94, 286, 83, 83, 424, 93, 83, 83, + 83, 84, 425, 83, 180, 83, 156, 408, 83, 300, + 451, 194, 233, 83, 83, 247, 264, 300, 352, 223, + 183, 268, 0, 83, 83, 83, 83, 247, 83, 300, + 176, 83, 174, 83, 405, 175, 186, 0, 181, 226, + 83, 0, 448, 83, 0, 83, 303, 424, 282, 83, + 296, 425, 296, 83, 301, 268, 383, 268, 268, 384, + 288, 0, 0, 0, 83, 83, 328, 0, 83, 268, + 268, 83, 295, 268, 298, 293, 268, 271, 287, 83, + 83, 0, 314, 296, 268, 268, 276, 83, 268, 0, + 296, 296, 268, 291, 289, 268, 268, 0, 0, 0, + 0, 0, 0, 0, 0, 315, 0, 0, 0, 0, + 0, 0, 317, 324, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 83, 0, 0, 0, 0, 268, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 311, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0}; + +const int QScriptGrammar::action_check [] = { + 29, 7, 16, 7, 76, 1, 36, 2, 29, 33, + 7, 61, 60, 7, 7, 48, 8, 36, 36, 55, + 61, 60, 2, 33, 29, 60, 5, 29, 36, 7, + 7, 76, 1, 61, 17, 48, 7, 7, 29, 55, + 8, 5, 31, 33, 55, 5, 7, 74, 36, 36, + 36, 36, 55, 29, 36, 7, 29, 8, 8, 17, + 8, 36, 29, 8, 36, 7, 29, 33, 33, 2, + 2, 55, 1, 7, 36, 76, 33, 20, 1, 60, + 29, 36, 29, 29, 8, 0, 60, 8, 8, 48, + 29, 8, 36, 33, 8, 8, 8, 36, 8, 40, + 8, 8, -1, 8, 6, 8, 42, 61, 62, 10, + 51, 8, 50, 7, 8, 40, 54, 53, 20, 8, + 76, 12, 40, 50, 61, 62, 51, 54, -1, -1, + 61, 62, 7, 51, 40, 56, 29, 15, 8, -1, + 60, 29, 25, 60, 27, 51, 60, 60, 29, 61, + 60, 56, 60, 60, 55, 38, 34, 60, 36, 56, + -1, 61, 62, 15, -1, 12, 57, 12, 61, 62, + 8, 60, 63, 61, 62, 25, 25, 27, 27, 15, + 61, 62, 34, 25, 36, 27, 8, -1, 38, 38, + -1, 61, 62, 25, 29, 27, 38, -1, 34, -1, + 36, 25, 25, 27, 27, -1, 38, 18, 19, 25, + 57, 27, 57, -1, 38, 38, 63, -1, 63, 18, + 19, -1, 38, 61, 62, -1, 61, 62, 18, 19, + 18, 19, -1, -1, 45, 46, 23, 24, -1, 61, + 62, -1, -1, -1, -1, 32, 45, 46, 35, -1, + 37, 23, 24, -1, -1, 45, 46, 45, 46, -1, + 32, -1, -1, 35, -1, 37, -1, -1, 25, -1, + 27, 23, 24, -1, -1, -1, -1, -1, -1, 31, + 32, 38, -1, 35, -1, 37, 23, 24, -1, -1, + -1, -1, -1, -1, 31, 32, 23, 24, 35, -1, + 37, -1, -1, -1, 31, 32, 23, 24, 35, 3, + 37, -1, -1, -1, 31, 32, -1, -1, 35, 13, + 37, 23, 24, 17, -1, -1, -1, 23, 24, 31, + 32, -1, 26, 35, 28, 37, 32, -1, -1, 35, + -1, 37, -1, -1, -1, 39, 3, 41, 42, -1, + -1, -1, -1, -1, -1, 49, 13, -1, 52, 53, + 17, -1, -1, -1, 58, -1, -1, -1, -1, 26, + 64, 28, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 39, 77, 41, 42, -1, -1, 12, 13, + 3, -1, 49, -1, -1, 52, 53, -1, 22, -1, + 13, 58, -1, -1, 17, 29, -1, 64, -1, 33, + 34, -1, 36, 26, -1, 28, -1, -1, 31, 43, + 77, -1, -1, 47, -1, -1, 39, -1, 41, 42, + -1, -1, -1, -1, -1, -1, 49, -1, -1, 52, + 53, 65, -1, 67, -1, 58, -1, -1, -1, -1, + -1, 64, -1, -1, 78, 79, 80, -1, -1, -1, + 12, 13, -1, -1, 77, -1, -1, -1, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, -1, -1, -1, -1, -1, + -1, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 65, -1, 67, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 78, 79, 80, -1, + -1, -1, 11, 12, 13, -1, -1, -1, -1, -1, + -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, + 29, -1, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 65, -1, 67, -1, + 69, -1, 71, -1, 73, -1, -1, -1, -1, 78, + 79, 80, -1, -1, -1, 11, 12, 13, -1, -1, + -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, + -1, -1, -1, 29, -1, -1, -1, 33, 34, -1, + 36, -1, -1, -1, 40, -1, 42, 43, 44, -1, + -1, 47, -1, -1, -1, 51, -1, 53, -1, -1, + 56, -1, -1, -1, -1, -1, -1, -1, -1, 65, + -1, 67, -1, 69, -1, 71, -1, 73, -1, -1, + -1, -1, 78, 79, 80, -1, -1, -1, 7, -1, + -1, -1, 11, 12, 13, -1, -1, -1, -1, -1, + -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, + 29, -1, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 65, -1, 67, -1, + 69, -1, 71, -1, 73, -1, -1, -1, -1, 78, + 79, 80, -1, -1, -1, 11, 12, 13, -1, -1, + -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, + -1, -1, -1, 29, -1, -1, -1, 33, 34, -1, + 36, -1, -1, -1, 40, -1, 42, 43, 44, -1, + -1, 47, -1, -1, -1, 51, -1, 53, -1, -1, + 56, -1, -1, -1, -1, -1, -1, -1, -1, 65, + -1, 67, -1, 69, -1, 71, -1, 73, -1, -1, + -1, -1, 78, 79, 80, -1, -1, -1, 11, 12, + 13, -1, -1, -1, -1, -1, -1, -1, -1, 22, + -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, + 33, 34, -1, 36, -1, -1, -1, 40, -1, 42, + 43, 44, -1, -1, 47, -1, -1, -1, 51, -1, + 53, -1, -1, -1, -1, -1, -1, 60, -1, -1, + -1, -1, 65, -1, 67, -1, 69, -1, 71, -1, + 73, -1, -1, -1, -1, 78, 79, 80, -1, -1, + -1, 11, 12, 13, -1, -1, -1, -1, -1, -1, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 65, -1, 67, -1, 69, + -1, 71, 72, 73, -1, -1, -1, -1, 78, 79, + 80, -1, -1, -1, 4, 5, 6, -1, -1, 9, + 10, 11, -1, -1, 14, -1, 16, -1, -1, -1, + 20, 21, 22, -1, -1, -1, -1, -1, -1, 29, + 30, 31, 32, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 43, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 59, + -1, -1, -1, -1, -1, -1, 66, 67, 68, -1, + 70, 71, 72, 73, 74, 75, -1, -1, 78, 79, + 80, 81, 82, 83, 4, 5, 6, -1, -1, 9, + 10, 11, -1, -1, 14, -1, 16, -1, -1, -1, + 20, 21, 22, -1, -1, -1, -1, -1, -1, 29, + 30, 31, 32, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 43, -1, -1, -1, 47, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 59, + -1, -1, -1, -1, -1, 65, 66, 67, 68, -1, + 70, 71, 72, 73, 74, 75, -1, -1, 78, 79, + 80, 81, 82, 83, 4, -1, -1, -1, -1, 9, + -1, 11, 12, 13, 14, -1, -1, -1, -1, -1, + -1, 21, 22, -1, -1, -1, -1, -1, -1, 29, + 30, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, -1, -1, -1, -1, 59, + -1, 61, -1, -1, -1, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, -1, -1, 78, 79, + 80, 81, 82, -1, 4, -1, -1, -1, -1, 9, + -1, 11, 12, 13, 14, -1, -1, -1, -1, -1, + -1, 21, 22, -1, -1, -1, -1, -1, -1, 29, + 30, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, -1, -1, -1, -1, 59, + -1, 61, -1, -1, -1, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, -1, -1, 78, 79, + 80, 81, 82, -1, 4, 5, 6, -1, -1, 9, + 10, 11, 12, 13, 14, -1, 16, -1, -1, -1, + 20, 21, 22, -1, -1, -1, -1, -1, -1, 29, + 30, 31, 32, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, 55, -1, -1, -1, 59, + -1, 61, -1, -1, -1, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, -1, -1, 78, 79, + 80, 81, 82, 83, + + 5, 46, 5, 45, 6, 45, 5, 76, 14, 65, + 2, 46, 5, 45, 73, 6, 5, 46, 6, 5, + 78, 6, 45, 5, 85, 6, 10, 6, 5, 9, + 6, 6, 6, 45, 6, 86, 14, 41, 45, 86, + 5, 5, 5, 80, 6, 46, 67, 45, 45, 5, + 81, 45, 5, 5, 5, 45, -1, 18, 45, 18, + 18, 18, 45, 18, 23, 26, 24, 24, 23, -1, + 18, 18, 45, 18, 45, 23, 23, 18, 23, 18, + 18, 18, 20, 5, 18, 24, 23, 28, 18, 23, + 20, 42, 18, 18, 20, 20, -1, 18, 18, 20, + 18, 45, 20, 23, 18, 18, 20, 20, 18, 18, + 18, 21, 20, 18, 23, 18, 21, 61, 18, 5, + 20, 10, 11, 18, 18, 20, 18, 5, 45, 32, + 24, 23, -1, 18, 18, 18, 18, 20, 18, 5, + 22, 18, 22, 18, 61, 22, 30, -1, 23, 34, + 18, -1, 20, 18, -1, 18, 42, 20, 23, 18, + 18, 20, 18, 18, 42, 23, 12, 23, 23, 15, + 25, -1, -1, -1, 18, 18, 42, -1, 18, 23, + 23, 18, 40, 23, 40, 29, 23, 27, 25, 18, + 18, -1, 35, 18, 23, 23, 25, 18, 23, -1, + 18, 18, 23, 31, 25, 23, 23, -1, -1, -1, + -1, -1, -1, -1, -1, 40, -1, -1, -1, -1, + -1, -1, 40, 40, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 18, -1, -1, -1, -1, 23, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 33, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1}; + + +#define Q_SCRIPT_REGEXPLITERAL_RULE1 7 + +#define Q_SCRIPT_REGEXPLITERAL_RULE2 8 + +#include "translator.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qnumeric.h> +#include <QtCore/qstring.h> +#include <QtCore/qtextcodec.h> +#include <QtCore/qvariant.h> + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +QT_BEGIN_NAMESPACE + +static void recordMessage( + Translator *tor, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, bool plural, const QString &fileName, int lineNo) +{ + TranslatorMessage msg( + context, text, comment, QString(), + fileName, lineNo, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + tor->replace(msg); +} + + +namespace QScript +{ + +class Lexer +{ +public: + Lexer(); + ~Lexer(); + + void setCode(const QString &c, int lineno); + int lex(); + + int currentLineNo() const { return yylineno; } + int currentColumnNo() const { return yycolumn; } + + int startLineNo() const { return startlineno; } + int startColumnNo() const { return startcolumn; } + + int endLineNo() const { return currentLineNo(); } + int endColumnNo() const + { int col = currentColumnNo(); return (col > 0) ? col - 1 : col; } + + bool prevTerminator() const { return terminator; } + + enum State { Start, + Identifier, + InIdentifier, + InSingleLineComment, + InMultiLineComment, + InNum, + InNum0, + InHex, + InOctal, + InDecimal, + InExponentIndicator, + InExponent, + Hex, + Octal, + Number, + String, + Eof, + InString, + InEscapeSequence, + InHexEscape, + InUnicodeEscape, + Other, + Bad }; + + enum Error { + NoError, + IllegalCharacter, + UnclosedStringLiteral, + IllegalEscapeSequence, + IllegalUnicodeEscapeSequence, + UnclosedComment, + IllegalExponentIndicator, + IllegalIdentifier + }; + + enum ParenthesesState { + IgnoreParentheses, + CountParentheses, + BalancedParentheses + }; + + enum RegExpBodyPrefix { + NoPrefix, + EqualPrefix + }; + + bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); + + QString pattern; + int flags; + + State lexerState() const + { return state; } + + QString errorMessage() const + { return errmsg; } + void setErrorMessage(const QString &err) + { errmsg = err; } + void setErrorMessage(const char *err) + { setErrorMessage(QString::fromLatin1(err)); } + + Error error() const + { return err; } + void clearError() + { err = NoError; } + +private: + int yylineno; + bool done; + char *buffer8; + QChar *buffer16; + uint size8, size16; + uint pos8, pos16; + bool terminator; + bool restrKeyword; + // encountered delimiter like "'" and "}" on last run + bool delimited; + int stackToken; + + State state; + void setDone(State s); + uint pos; + void shift(uint p); + int lookupKeyword(const char *); + + bool isWhiteSpace() const; + bool isLineTerminator() const; + bool isHexDigit(ushort c) const; + bool isOctalDigit(ushort c) const; + + int matchPunctuator(ushort c1, ushort c2, + ushort c3, ushort c4); + ushort singleEscape(ushort c) const; + ushort convertOctal(ushort c1, ushort c2, + ushort c3) const; +public: + static unsigned char convertHex(ushort c1); + static unsigned char convertHex(ushort c1, ushort c2); + static QChar convertUnicode(ushort c1, ushort c2, + ushort c3, ushort c4); + static bool isIdentLetter(ushort c); + static bool isDecimalDigit(ushort c); + + inline int ival() const { return qsyylval.toInt(); } + inline double dval() const { return qsyylval.toDouble(); } + inline QString ustr() const { return qsyylval.toString(); } + inline QVariant val() const { return qsyylval; } + + const QChar *characterBuffer() const { return buffer16; } + int characterCount() const { return pos16; } + +private: + void record8(ushort c); + void record16(QChar c); + void recordStartPos(); + + int findReservedWord(const QChar *buffer, int size) const; + + void syncProhibitAutomaticSemicolon(); + + const QChar *code; + uint length; + int yycolumn; + int startlineno; + int startcolumn; + int bol; // begin of line + + QVariant qsyylval; + + // current and following unicode characters + ushort current, next1, next2, next3; + + struct keyword { + const char *name; + int token; + }; + + QString errmsg; + Error err; + + bool wantRx; + bool check_reserved; + + ParenthesesState parenthesesState; + int parenthesesCount; + bool prohibitAutomaticSemicolon; +}; + +} // namespace QScript + +extern double qstrtod(const char *s00, char const **se, bool *ok); + +#define shiftWindowsLineBreak() if(current == '\r' && next1 == '\n') shift(1); + +namespace QScript { + +static int toDigit(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'a') && (c <= 'z')) + return 10 + c - 'a'; + else if ((c >= 'A') && (c <= 'Z')) + return 10 + c - 'A'; + return -1; +} + +double integerFromString(const char *buf, int size, int radix) +{ + if (size == 0) + return qSNaN(); + + double sign = 1.0; + int i = 0; + if (buf[0] == '+') { + ++i; + } else if (buf[0] == '-') { + sign = -1.0; + ++i; + } + + if (((size-i) >= 2) && (buf[i] == '0')) { + if (((buf[i+1] == 'x') || (buf[i+1] == 'X')) + && (radix < 34)) { + if ((radix != 0) && (radix != 16)) + return 0; + radix = 16; + i += 2; + } else { + if (radix == 0) { + radix = 8; + ++i; + } + } + } else if (radix == 0) { + radix = 10; + } + + int j = i; + for ( ; i < size; ++i) { + int d = toDigit(buf[i]); + if ((d == -1) || (d >= radix)) + break; + } + double result; + if (j == i) { + if (!qstrcmp(buf, "Infinity")) + result = qInf(); + else + result = qSNaN(); + } else { + result = 0; + double multiplier = 1; + for (--i ; i >= j; --i, multiplier *= radix) + result += toDigit(buf[i]) * multiplier; + } + result *= sign; + return result; +} + +} // namespace QScript + +QScript::Lexer::Lexer() + : + yylineno(0), + size8(128), size16(128), restrKeyword(false), + stackToken(-1), pos(0), + code(0), length(0), + bol(true), + current(0), next1(0), next2(0), next3(0), + err(NoError), + check_reserved(true), + parenthesesState(IgnoreParentheses), + prohibitAutomaticSemicolon(false) +{ + // allocate space for read buffers + buffer8 = new char[size8]; + buffer16 = new QChar[size16]; + flags = 0; + +} + +QScript::Lexer::~Lexer() +{ + delete [] buffer8; + delete [] buffer16; +} + +void QScript::Lexer::setCode(const QString &c, int lineno) +{ + errmsg = QString(); + yylineno = lineno; + yycolumn = 1; + restrKeyword = false; + delimited = false; + stackToken = -1; + pos = 0; + code = c.unicode(); + length = c.length(); + bol = true; + + // read first characters + current = (length > 0) ? code[0].unicode() : 0; + next1 = (length > 1) ? code[1].unicode() : 0; + next2 = (length > 2) ? code[2].unicode() : 0; + next3 = (length > 3) ? code[3].unicode() : 0; +} + +void QScript::Lexer::shift(uint p) +{ + while (p--) { + ++pos; + ++yycolumn; + current = next1; + next1 = next2; + next2 = next3; + next3 = (pos + 3 < length) ? code[pos+3].unicode() : 0; + } +} + +void QScript::Lexer::setDone(State s) +{ + state = s; + done = true; +} + +int QScript::Lexer::findReservedWord(const QChar *c, int size) const +{ + switch (size) { + case 2: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('o')) + return QScriptGrammar::T_DO; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('f')) + return QScriptGrammar::T_IF; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n')) + return QScriptGrammar::T_IN; + } break; + + case 3: { + if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('o') && c[2] == QLatin1Char('r')) + return QScriptGrammar::T_FOR; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('e') && c[2] == QLatin1Char('w')) + return QScriptGrammar::T_NEW; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') && c[2] == QLatin1Char('y')) + return QScriptGrammar::T_TRY; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('a') && c[2] == QLatin1Char('r')) + return QScriptGrammar::T_VAR; + else if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') && c[2] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 4: { + if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_CASE; + else if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_ELSE; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('s')) + return QScriptGrammar::T_THIS; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('d')) + return QScriptGrammar::T_VOID; + else if (c[0] == QLatin1Char('w') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('h')) + return QScriptGrammar::T_WITH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_TRUE; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('l')) + return QScriptGrammar::T_NULL; + else if (check_reserved) { + if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('m')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('l') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('g')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('r')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('g') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('o')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 5: { + if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('e') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('k')) + return QScriptGrammar::T_BREAK; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('h')) + return QScriptGrammar::T_CATCH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('r') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('w')) + return QScriptGrammar::T_THROW; + else if (c[0] == QLatin1Char('w') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e')) + return QScriptGrammar::T_WHILE; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_CONST; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('e')) + return QScriptGrammar::T_FALSE; + else if (check_reserved) { + if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('r') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('r')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('l')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 6: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_DELETE; + else if (c[0] == QLatin1Char('r') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('u') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('n')) + return QScriptGrammar::T_RETURN; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('w') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('c') && c[5] == QLatin1Char('h')) + return QScriptGrammar::T_SWITCH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('o') && c[5] == QLatin1Char('f')) + return QScriptGrammar::T_TYPEOF; + else if (check_reserved) { + if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('x') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('t') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('c')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('b') + && c[4] == QLatin1Char('l') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('m') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('b') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('c')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('i') + && c[4] == QLatin1Char('v') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('r') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('w') && c[5] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 7: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('f') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('u') && c[5] == QLatin1Char('l') + && c[6] == QLatin1Char('t')) + return QScriptGrammar::T_DEFAULT; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('l') && c[5] == QLatin1Char('l') + && c[6] == QLatin1Char('y')) + return QScriptGrammar::T_FINALLY; + else if (check_reserved) { + if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('a') + && c[6] == QLatin1Char('n')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('x') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('n') && c[5] == QLatin1Char('d') + && c[6] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('c') && c[3] == QLatin1Char('k') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('g') + && c[6] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('v') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('t') + && c[6] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 8: { + if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('n') + && c[6] == QLatin1Char('u') && c[7] == QLatin1Char('e')) + return QScriptGrammar::T_CONTINUE; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('o') && c[7] == QLatin1Char('n')) + return QScriptGrammar::T_FUNCTION; + else if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('b') && c[3] == QLatin1Char('u') + && c[4] == QLatin1Char('g') && c[5] == QLatin1Char('g') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('r')) + return QScriptGrammar::T_DEBUGGER; + else if (check_reserved) { + if (c[0] == QLatin1Char('a') && c[1] == QLatin1Char('b') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('a') + && c[6] == QLatin1Char('c') && c[7] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('l') && c[7] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 9: { + if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('f') + && c[6] == QLatin1Char('a') && c[7] == QLatin1Char('c') + && c[8] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('n') + && c[4] == QLatin1Char('s') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('c') + && c[6] == QLatin1Char('t') && c[7] == QLatin1Char('e') + && c[8] == QLatin1Char('d')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 10: { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('n') + && c[6] == QLatin1Char('c') && c[7] == QLatin1Char('e') + && c[8] == QLatin1Char('o') && c[9] == QLatin1Char('f')) + return QScriptGrammar::T_INSTANCEOF; + else if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('m') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('m') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('t') && c[9] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 12: { + if (check_reserved) { + if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('h') && c[5] == QLatin1Char('r') + && c[6] == QLatin1Char('o') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('i') && c[9] == QLatin1Char('z') + && c[10] == QLatin1Char('e') && c[11] == QLatin1Char('d')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + } // switch + + return -1; +} + +int QScript::Lexer::lex() +{ + int token = 0; + state = Start; + ushort stringType = 0; // either single or double quotes + pos8 = pos16 = 0; + done = false; + terminator = false; + + // did we push a token on the stack previously ? + // (after an automatic semicolon insertion) + if (stackToken >= 0) { + setDone(Other); + token = stackToken; + stackToken = -1; + } + + while (!done) { + switch (state) { + case Start: + if (isWhiteSpace()) { + // do nothing + } else if (current == '/' && next1 == '/') { + recordStartPos(); + shift(1); + state = InSingleLineComment; + } else if (current == '/' && next1 == '*') { + recordStartPos(); + shift(1); + state = InMultiLineComment; + } else if (current == 0) { + syncProhibitAutomaticSemicolon(); + if (!terminator && !delimited && !prohibitAutomaticSemicolon) { + // automatic semicolon insertion if program incomplete + token = QScriptGrammar::T_SEMICOLON; + stackToken = 0; + setDone(Other); + } else { + setDone(Eof); + } + } else if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + yycolumn = 0; + bol = true; + terminator = true; + syncProhibitAutomaticSemicolon(); + if (restrKeyword) { + token = QScriptGrammar::T_SEMICOLON; + setDone(Other); + } + } else if (current == '"' || current == '\'') { + recordStartPos(); + state = InString; + stringType = current; + } else if (isIdentLetter(current)) { + recordStartPos(); + record16(current); + state = InIdentifier; + } else if (current == '0') { + recordStartPos(); + record8(current); + state = InNum0; + } else if (isDecimalDigit(current)) { + recordStartPos(); + record8(current); + state = InNum; + } else if (current == '.' && isDecimalDigit(next1)) { + recordStartPos(); + record8(current); + state = InDecimal; + } else { + recordStartPos(); + token = matchPunctuator(current, next1, next2, next3); + if (token != -1) { + if (terminator && !delimited && !prohibitAutomaticSemicolon + && (token == QScriptGrammar::T_PLUS_PLUS + || token == QScriptGrammar::T_MINUS_MINUS)) { + // automatic semicolon insertion + stackToken = token; + token = QScriptGrammar::T_SEMICOLON; + } + setDone(Other); + } + else { + setDone(Bad); + err = IllegalCharacter; + errmsg = QLatin1String("Illegal character"); + } + } + break; + case InString: + if (current == stringType) { + shift(1); + setDone(String); + } else if (current == 0 || isLineTerminator()) { + setDone(Bad); + err = UnclosedStringLiteral; + errmsg = QLatin1String("Unclosed string at end of line"); + } else if (current == '\\') { + state = InEscapeSequence; + } else { + record16(current); + } + break; + // Escape Sequences inside of strings + case InEscapeSequence: + if (isOctalDigit(current)) { + if (current >= '0' && current <= '3' && + isOctalDigit(next1) && isOctalDigit(next2)) { + record16(convertOctal(current, next1, next2)); + shift(2); + state = InString; + } else if (isOctalDigit(current) && + isOctalDigit(next1)) { + record16(convertOctal('0', current, next1)); + shift(1); + state = InString; + } else if (isOctalDigit(current)) { + record16(convertOctal('0', '0', current)); + state = InString; + } else { + setDone(Bad); + err = IllegalEscapeSequence; + errmsg = QLatin1String("Illegal escape squence"); + } + } else if (current == 'x') + state = InHexEscape; + else if (current == 'u') + state = InUnicodeEscape; + else { + record16(singleEscape(current)); + state = InString; + } + break; + case InHexEscape: + if (isHexDigit(current) && isHexDigit(next1)) { + state = InString; + record16(QLatin1Char(convertHex(current, next1))); + shift(1); + } else if (current == stringType) { + record16(QLatin1Char('x')); + shift(1); + setDone(String); + } else { + record16(QLatin1Char('x')); + record16(current); + state = InString; + } + break; + case InUnicodeEscape: + if (isHexDigit(current) && isHexDigit(next1) && + isHexDigit(next2) && isHexDigit(next3)) { + record16(convertUnicode(current, next1, next2, next3)); + shift(3); + state = InString; + } else if (current == stringType) { + record16(QLatin1Char('u')); + shift(1); + setDone(String); + } else { + setDone(Bad); + err = IllegalUnicodeEscapeSequence; + errmsg = QLatin1String("Illegal unicode escape sequence"); + } + break; + case InSingleLineComment: + if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + yycolumn = 0; + terminator = true; + bol = true; + if (restrKeyword) { + token = QScriptGrammar::T_SEMICOLON; + setDone(Other); + } else + state = Start; + } else if (current == 0) { + setDone(Eof); + } + break; + case InMultiLineComment: + if (current == 0) { + setDone(Bad); + err = UnclosedComment; + errmsg = QLatin1String("Unclosed comment at end of file"); + } else if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + } else if (current == '*' && next1 == '/') { + state = Start; + shift(1); + } + break; + case InIdentifier: + if (isIdentLetter(current) || isDecimalDigit(current)) { + record16(current); + break; + } + setDone(Identifier); + break; + case InNum0: + if (current == 'x' || current == 'X') { + record8(current); + state = InHex; + } else if (current == '.') { + record8(current); + state = InDecimal; + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else if (isOctalDigit(current)) { + record8(current); + state = InOctal; + } else if (isDecimalDigit(current)) { + record8(current); + state = InDecimal; + } else { + setDone(Number); + } + break; + case InHex: + if (isHexDigit(current)) + record8(current); + else + setDone(Hex); + break; + case InOctal: + if (isOctalDigit(current)) { + record8(current); + } else if (isDecimalDigit(current)) { + record8(current); + state = InDecimal; + } else { + setDone(Octal); + } + break; + case InNum: + if (isDecimalDigit(current)) { + record8(current); + } else if (current == '.') { + record8(current); + state = InDecimal; + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else { + setDone(Number); + } + break; + case InDecimal: + if (isDecimalDigit(current)) { + record8(current); + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else { + setDone(Number); + } + break; + case InExponentIndicator: + if (current == '+' || current == '-') { + record8(current); + } else if (isDecimalDigit(current)) { + record8(current); + state = InExponent; + } else { + setDone(Bad); + err = IllegalExponentIndicator; + errmsg = QLatin1String("Illegal syntax for exponential number"); + } + break; + case InExponent: + if (isDecimalDigit(current)) { + record8(current); + } else { + setDone(Number); + } + break; + default: + Q_ASSERT_X(0, "Lexer::lex", "Unhandled state in switch statement"); + } + + // move on to the next character + if (!done) + shift(1); + if (state != Start && state != InSingleLineComment) + bol = false; + } + + // no identifiers allowed directly after numeric literal, e.g. "3in" is bad + if ((state == Number || state == Octal || state == Hex) + && isIdentLetter(current)) { + state = Bad; + err = IllegalIdentifier; + errmsg = QLatin1String("Identifier cannot start with numeric literal"); + } + + // terminate string + buffer8[pos8] = '\0'; + + double dval = 0; + if (state == Number) { + dval = qstrtod(buffer8, 0, 0); + } else if (state == Hex) { // scan hex numbers + dval = QScript::integerFromString(buffer8, pos8, 16); + state = Number; + } else if (state == Octal) { // scan octal number + dval = QScript::integerFromString(buffer8, pos8, 8); + state = Number; + } + + restrKeyword = false; + delimited = false; + + switch (parenthesesState) { + case IgnoreParentheses: + break; + case CountParentheses: + if (token == QScriptGrammar::T_RPAREN) { + --parenthesesCount; + if (parenthesesCount == 0) + parenthesesState = BalancedParentheses; + } else if (token == QScriptGrammar::T_LPAREN) { + ++parenthesesCount; + } + break; + case BalancedParentheses: + parenthesesState = IgnoreParentheses; + break; + } + + switch (state) { + case Eof: + return 0; + case Other: + if(token == QScriptGrammar::T_RBRACE || token == QScriptGrammar::T_SEMICOLON) + delimited = true; + return token; + case Identifier: + if ((token = findReservedWord(buffer16, pos16)) < 0) { + /* TODO: close leak on parse error. same holds true for String */ + qsyylval = QString(buffer16, pos16); + return QScriptGrammar::T_IDENTIFIER; + } + if (token == QScriptGrammar::T_CONTINUE || token == QScriptGrammar::T_BREAK + || token == QScriptGrammar::T_RETURN || token == QScriptGrammar::T_THROW) { + restrKeyword = true; + } else if (token == QScriptGrammar::T_IF || token == QScriptGrammar::T_FOR + || token == QScriptGrammar::T_WHILE || token == QScriptGrammar::T_WITH) { + parenthesesState = CountParentheses; + parenthesesCount = 0; + } else if (token == QScriptGrammar::T_DO) { + parenthesesState = BalancedParentheses; + } + return token; + case String: + qsyylval = QString(buffer16, pos16); + return QScriptGrammar::T_STRING_LITERAL; + case Number: + qsyylval = dval; + return QScriptGrammar::T_NUMERIC_LITERAL; + case Bad: + return -1; + default: + Q_ASSERT(!"unhandled numeration value in switch"); + return -1; + } +} + +bool QScript::Lexer::isWhiteSpace() const +{ + return (current == ' ' || current == '\t' || + current == 0x0b || current == 0x0c); +} + +bool QScript::Lexer::isLineTerminator() const +{ + return (current == '\n' || current == '\r'); +} + +bool QScript::Lexer::isIdentLetter(ushort c) +{ + /* TODO: allow other legitimate unicode chars */ + return ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '$' + || c == '_'); +} + +bool QScript::Lexer::isDecimalDigit(ushort c) +{ + return (c >= '0' && c <= '9'); +} + +bool QScript::Lexer::isHexDigit(ushort c) const +{ + return ((c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F')); +} + +bool QScript::Lexer::isOctalDigit(ushort c) const +{ + return (c >= '0' && c <= '7'); +} + +int QScript::Lexer::matchPunctuator(ushort c1, ushort c2, + ushort c3, ushort c4) +{ + if (c1 == '>' && c2 == '>' && c3 == '>' && c4 == '=') { + shift(4); + return QScriptGrammar::T_GT_GT_GT_EQ; + } else if (c1 == '=' && c2 == '=' && c3 == '=') { + shift(3); + return QScriptGrammar::T_EQ_EQ_EQ; + } else if (c1 == '!' && c2 == '=' && c3 == '=') { + shift(3); + return QScriptGrammar::T_NOT_EQ_EQ; + } else if (c1 == '>' && c2 == '>' && c3 == '>') { + shift(3); + return QScriptGrammar::T_GT_GT_GT; + } else if (c1 == '<' && c2 == '<' && c3 == '=') { + shift(3); + return QScriptGrammar::T_LT_LT_EQ; + } else if (c1 == '>' && c2 == '>' && c3 == '=') { + shift(3); + return QScriptGrammar::T_GT_GT_EQ; + } else if (c1 == '<' && c2 == '=') { + shift(2); + return QScriptGrammar::T_LE; + } else if (c1 == '>' && c2 == '=') { + shift(2); + return QScriptGrammar::T_GE; + } else if (c1 == '!' && c2 == '=') { + shift(2); + return QScriptGrammar::T_NOT_EQ; + } else if (c1 == '+' && c2 == '+') { + shift(2); + return QScriptGrammar::T_PLUS_PLUS; + } else if (c1 == '-' && c2 == '-') { + shift(2); + return QScriptGrammar::T_MINUS_MINUS; + } else if (c1 == '=' && c2 == '=') { + shift(2); + return QScriptGrammar::T_EQ_EQ; + } else if (c1 == '+' && c2 == '=') { + shift(2); + return QScriptGrammar::T_PLUS_EQ; + } else if (c1 == '-' && c2 == '=') { + shift(2); + return QScriptGrammar::T_MINUS_EQ; + } else if (c1 == '*' && c2 == '=') { + shift(2); + return QScriptGrammar::T_STAR_EQ; + } else if (c1 == '/' && c2 == '=') { + shift(2); + return QScriptGrammar::T_DIVIDE_EQ; + } else if (c1 == '&' && c2 == '=') { + shift(2); + return QScriptGrammar::T_AND_EQ; + } else if (c1 == '^' && c2 == '=') { + shift(2); + return QScriptGrammar::T_XOR_EQ; + } else if (c1 == '%' && c2 == '=') { + shift(2); + return QScriptGrammar::T_REMAINDER_EQ; + } else if (c1 == '|' && c2 == '=') { + shift(2); + return QScriptGrammar::T_OR_EQ; + } else if (c1 == '<' && c2 == '<') { + shift(2); + return QScriptGrammar::T_LT_LT; + } else if (c1 == '>' && c2 == '>') { + shift(2); + return QScriptGrammar::T_GT_GT; + } else if (c1 == '&' && c2 == '&') { + shift(2); + return QScriptGrammar::T_AND_AND; + } else if (c1 == '|' && c2 == '|') { + shift(2); + return QScriptGrammar::T_OR_OR; + } + + switch(c1) { + case '=': shift(1); return QScriptGrammar::T_EQ; + case '>': shift(1); return QScriptGrammar::T_GT; + case '<': shift(1); return QScriptGrammar::T_LT; + case ',': shift(1); return QScriptGrammar::T_COMMA; + case '!': shift(1); return QScriptGrammar::T_NOT; + case '~': shift(1); return QScriptGrammar::T_TILDE; + case '?': shift(1); return QScriptGrammar::T_QUESTION; + case ':': shift(1); return QScriptGrammar::T_COLON; + case '.': shift(1); return QScriptGrammar::T_DOT; + case '+': shift(1); return QScriptGrammar::T_PLUS; + case '-': shift(1); return QScriptGrammar::T_MINUS; + case '*': shift(1); return QScriptGrammar::T_STAR; + case '/': shift(1); return QScriptGrammar::T_DIVIDE_; + case '&': shift(1); return QScriptGrammar::T_AND; + case '|': shift(1); return QScriptGrammar::T_OR; + case '^': shift(1); return QScriptGrammar::T_XOR; + case '%': shift(1); return QScriptGrammar::T_REMAINDER; + case '(': shift(1); return QScriptGrammar::T_LPAREN; + case ')': shift(1); return QScriptGrammar::T_RPAREN; + case '{': shift(1); return QScriptGrammar::T_LBRACE; + case '}': shift(1); return QScriptGrammar::T_RBRACE; + case '[': shift(1); return QScriptGrammar::T_LBRACKET; + case ']': shift(1); return QScriptGrammar::T_RBRACKET; + case ';': shift(1); return QScriptGrammar::T_SEMICOLON; + + default: return -1; + } +} + +ushort QScript::Lexer::singleEscape(ushort c) const +{ + switch(c) { + case 'b': + return 0x08; + case 't': + return 0x09; + case 'n': + return 0x0A; + case 'v': + return 0x0B; + case 'f': + return 0x0C; + case 'r': + return 0x0D; + case '"': + return 0x22; + case '\'': + return 0x27; + case '\\': + return 0x5C; + default: + return c; + } +} + +ushort QScript::Lexer::convertOctal(ushort c1, ushort c2, + ushort c3) const +{ + return ((c1 - '0') * 64 + (c2 - '0') * 8 + c3 - '0'); +} + +unsigned char QScript::Lexer::convertHex(ushort c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (c - 'A' + 10); +} + +unsigned char QScript::Lexer::convertHex(ushort c1, ushort c2) +{ + return ((convertHex(c1) << 4) + convertHex(c2)); +} + +QChar QScript::Lexer::convertUnicode(ushort c1, ushort c2, + ushort c3, ushort c4) +{ + return QChar((convertHex(c3) << 4) + convertHex(c4), + (convertHex(c1) << 4) + convertHex(c2)); +} + +void QScript::Lexer::record8(ushort c) +{ + Q_ASSERT(c <= 0xff); + + // enlarge buffer if full + if (pos8 >= size8 - 1) { + char *tmp = new char[2 * size8]; + memcpy(tmp, buffer8, size8 * sizeof(char)); + delete [] buffer8; + buffer8 = tmp; + size8 *= 2; + } + + buffer8[pos8++] = (char) c; +} + +void QScript::Lexer::record16(QChar c) +{ + // enlarge buffer if full + if (pos16 >= size16 - 1) { + QChar *tmp = new QChar[2 * size16]; + memcpy(tmp, buffer16, size16 * sizeof(QChar)); + delete [] buffer16; + buffer16 = tmp; + size16 *= 2; + } + + buffer16[pos16++] = c; +} + +void QScript::Lexer::recordStartPos() +{ + startlineno = yylineno; + startcolumn = yycolumn; +} + +bool QScript::Lexer::scanRegExp(RegExpBodyPrefix prefix) +{ + pos16 = 0; + bool lastWasEscape = false; + + if (prefix == EqualPrefix) + record16(QLatin1Char('=')); + + while (1) { + if (isLineTerminator() || current == 0) { + errmsg = QLatin1String("Unterminated regular expression literal"); + return false; + } + else if (current != '/' || lastWasEscape == true) + { + record16(current); + lastWasEscape = !lastWasEscape && (current == '\\'); + } + else { + pattern = QString(buffer16, pos16); + pos16 = 0; + shift(1); + break; + } + shift(1); + } + + flags = 0; + while (isIdentLetter(current)) { + record16(current); + shift(1); + } + + return true; +} + +void QScript::Lexer::syncProhibitAutomaticSemicolon() +{ + if (parenthesesState == BalancedParentheses) { + // we have seen something like "if (foo)", which means we should + // never insert an automatic semicolon at this point, since it would + // then be expanded into an empty statement (ECMA-262 7.9.1) + prohibitAutomaticSemicolon = true; + parenthesesState = IgnoreParentheses; + } else { + prohibitAutomaticSemicolon = false; + } +} + + +class Translator; + +class QScriptParser: protected QScriptGrammar +{ +public: + QVariant val; + + struct Location { + int startLine; + int startColumn; + int endLine; + int endColumn; + }; + +public: + QScriptParser(); + ~QScriptParser(); + + bool parse(QScript::Lexer *lexer, + const QString &fileName, + Translator *translator); + + inline QString errorMessage() const + { return error_message; } + inline int errorLineNumber() const + { return error_lineno; } + inline int errorColumnNumber() const + { return error_column; } + +protected: + inline void reallocateStack(); + + inline QVariant &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline Location &loc(int index) + { return location_stack [tos + index - 2]; } + +protected: + int tos; + int stack_size; + QVector<QVariant> sym_stack; + int *state_stack; + Location *location_stack; + QString error_message; + int error_lineno; + int error_column; +}; + +inline void QScriptParser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack.resize(stack_size); + state_stack = reinterpret_cast<int*> (qRealloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast<Location*> (qRealloc(location_stack, stack_size * sizeof(Location))); +} + +inline static bool automatic(QScript::Lexer *lexer, int token) +{ + return (token == QScriptGrammar::T_RBRACE) + || (token == 0) + || lexer->prevTerminator(); +} + +QScriptParser::QScriptParser(): + tos(0), + stack_size(0), + sym_stack(0), + state_stack(0), + location_stack(0) +{ +} + +QScriptParser::~QScriptParser() +{ + if (stack_size) { + qFree(state_stack); + qFree(location_stack); + } +} + +static inline QScriptParser::Location location(QScript::Lexer *lexer) +{ + QScriptParser::Location loc; + loc.startLine = lexer->startLineNo(); + loc.startColumn = lexer->startColumnNo(); + loc.endLine = lexer->endLineNo(); + loc.endColumn = lexer->endColumnNo(); + return loc; +} + +bool QScriptParser::parse(QScript::Lexer *lexer, + const QString &fileName, + Translator *translator) +{ + const int INITIAL_STATE = 0; + + int yytoken = -1; + int saved_yytoken = -1; + int identLineNo = -1; + + reallocateStack(); + + tos = 0; + state_stack[++tos] = INITIAL_STATE; + + while (true) + { + const int state = state_stack [tos]; + if (yytoken == -1 && - TERMINAL_COUNT != action_index [state]) + { + if (saved_yytoken == -1) + { + yytoken = lexer->lex(); + location_stack [tos] = location(lexer); + } + else + { + yytoken = saved_yytoken; + saved_yytoken = -1; + } + } + + int act = t_action (state, yytoken); + + if (act == ACCEPT_STATE) + return true; + + else if (act > 0) + { + if (++tos == stack_size) + reallocateStack(); + + sym_stack [tos] = lexer->val (); + state_stack [tos] = act; + location_stack [tos] = location(lexer); + yytoken = -1; + } + + else if (act < 0) + { + int r = - act - 1; + + tos -= rhs [r]; + act = state_stack [tos++]; + + switch (r) { + +case 1: { + sym(1) = sym(1).toByteArray(); + identLineNo = lexer->startLineNo(); +} break; + +case 7: { + bool rx = lexer->scanRegExp(QScript::Lexer::NoPrefix); + if (!rx) { + error_message = lexer->errorMessage(); + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + return false; + } +} break; + +case 8: { + bool rx = lexer->scanRegExp(QScript::Lexer::EqualPrefix); + if (!rx) { + error_message = lexer->errorMessage(); + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + return false; + } +} break; + +case 66: { + QString name = sym(1).toString(); + if ((name == QLatin1String("qsTranslate")) || (name == QLatin1String("QT_TRANSLATE_NOOP"))) { + QVariantList args = sym(2).toList(); + if (args.size() < 2) { + qWarning("%s:%d: %s() requires at least two arguments", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + if ((args.at(0).type() != QVariant::String) + || (args.at(1).type() != QVariant::String)) { + qWarning("%s:%d: %s(): both arguments must be literal strings", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + QString context = args.at(0).toString(); + QString text = args.at(1).toString(); + QString comment = args.value(2).toString(); + QString extracomment; + bool plural = (args.size() > 4); + recordMessage(translator, context, text, comment, extracomment, + plural, fileName, identLineNo); + } + } + } else if ((name == QLatin1String("qsTr")) || (name == QLatin1String("QT_TR_NOOP"))) { + QVariantList args = sym(2).toList(); + if (args.size() < 1) { + qWarning("%s:%d: %s() requires at least one argument", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + if (args.at(0).type() != QVariant::String) { + qWarning("%s:%d: %s(): text to translate must be a literal string", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + QString context = QFileInfo(fileName).baseName(); + QString text = args.at(0).toString(); + QString comment = args.value(1).toString(); + QString extracomment; + bool plural = (args.size() > 2); + recordMessage(translator, context, text, comment, extracomment, + plural, fileName, identLineNo); + } + } + } +} break; + +case 70: { + sym(1) = QVariantList(); +} break; + +case 71: { + sym(1) = sym(2); +} break; + +case 72: { + sym(1) = QVariantList() << sym(1); +} break; + +case 73: { + sym(1) = sym(1).toList() << sym(3); +} break; + +case 94: { + if ((sym(1).type() == QVariant::String) || (sym(3).type() == QVariant::String)) + sym(1) = sym(1).toString() + sym(3).toString(); + else + sym(1) = QVariant(); +} break; + + } // switch + + state_stack [tos] = nt_action (act, lhs [r] - TERMINAL_COUNT); + + if (rhs[r] > 1) { + location_stack[tos - 1].endLine = location_stack[tos + rhs[r] - 2].endLine; + location_stack[tos - 1].endColumn = location_stack[tos + rhs[r] - 2].endColumn; + location_stack[tos] = location_stack[tos + rhs[r] - 1]; + } + } + + else + { + if (saved_yytoken == -1 && automatic (lexer, yytoken) && t_action (state, T_AUTOMATIC_SEMICOLON) > 0) + { + saved_yytoken = yytoken; + yytoken = T_SEMICOLON; + continue; + } + + else if ((state == INITIAL_STATE) && (yytoken == 0)) { + // accept empty input + yytoken = T_SEMICOLON; + continue; + } + + int ers = state; + int shifts = 0; + int reduces = 0; + int expected_tokens [3]; + for (int tk = 0; tk < TERMINAL_COUNT; ++tk) + { + int k = t_action (ers, tk); + + if (! k) + continue; + else if (k < 0) + ++reduces; + else if (spell [tk]) + { + if (shifts < 3) + expected_tokens [shifts] = tk; + ++shifts; + } + } + + error_message.clear (); + if (shifts && shifts < 3) + { + bool first = true; + + for (int s = 0; s < shifts; ++s) + { + if (first) + error_message += QLatin1String ("Expected "); + else + error_message += QLatin1String (", "); + + first = false; + error_message += QLatin1String("`"); + error_message += QLatin1String (spell [expected_tokens [s]]); + error_message += QLatin1String("'"); + } + } + + if (error_message.isEmpty()) + error_message = lexer->errorMessage(); + + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + + return false; + } + } + + return false; +} + + +bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QTextStream ts(&dev); + QByteArray codecName; + if (!cd.m_codecForSource.isEmpty()) + codecName = cd.m_codecForSource; + else + codecName = translator.codecName(); // Just because it should be latin1 already + ts.setCodec(QTextCodec::codecForName(codecName)); + ts.setAutoDetectUnicode(true); + + QString code = ts.readAll(); + QScript::Lexer lexer; + lexer.setCode(code, /*lineNumber=*/1); + QScriptParser parser; + if (!parser.parse(&lexer, cd.m_sourceFileName, &translator)) { + qWarning("%s:%d: %s", qPrintable(cd.m_sourceFileName), parser.errorLineNumber(), + qPrintable(parser.errorMessage())); + return false; + } + + // Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it. + translator.setCodecName("UTF-8"); + return true; +} + +bool saveQScript(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + Q_UNUSED(dev); + Q_UNUSED(translator); + cd.appendError(QLatin1String("Cannot save .js files")); + return false; +} + +int initQScript() +{ + Translator::FileFormat format; + format.extension = QLatin1String("js"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.description = QObject::tr("Qt Script source files"); + format.loader = &loadQScript; + format.saver = &saveQScript; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initQScript) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/qscript.g b/tools/linguist/shared/qscript.g new file mode 100644 index 0000000..c7ee80d --- /dev/null +++ b/tools/linguist/shared/qscript.g @@ -0,0 +1,2039 @@ +---------------------------------------------------------------------------- +-- +-- Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +-- Contact: Qt Software Information (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 either Technology Preview License Agreement or the +-- Beta Release License Agreement. +-- +-- 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.0, 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. +-- +-- If you are unsure which license is appropriate for your use, please +-- contact the sales department at qt-sales@nokia.com. +-- $QT_END_LICENSE$ +-- +-- This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +-- WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +-- +---------------------------------------------------------------------------- + +%parser QScriptGrammar +%merged_output qscript.cpp +%expect 3 +%expect-rr 1 + +%token T_AND "&" T_AND_AND "&&" T_AND_EQ "&=" +%token T_BREAK "break" T_CASE "case" T_CATCH "catch" +%token T_COLON ":" T_COMMA ";" T_CONTINUE "continue" +%token T_DEFAULT "default" T_DELETE "delete" T_DIVIDE_ "/" +%token T_DIVIDE_EQ "/=" T_DO "do" T_DOT "." +%token T_ELSE "else" T_EQ "=" T_EQ_EQ "==" +%token T_EQ_EQ_EQ "===" T_FINALLY "finally" T_FOR "for" +%token T_FUNCTION "function" T_GE ">=" T_GT ">" +%token T_GT_GT ">>" T_GT_GT_EQ ">>=" T_GT_GT_GT ">>>" +%token T_GT_GT_GT_EQ ">>>=" T_IDENTIFIER "identifier" T_IF "if" +%token T_IN "in" T_INSTANCEOF "instanceof" T_LBRACE "{" +%token T_LBRACKET "[" T_LE "<=" T_LPAREN "(" +%token T_LT "<" T_LT_LT "<<" T_LT_LT_EQ "<<=" +%token T_MINUS "-" T_MINUS_EQ "-=" T_MINUS_MINUS "--" +%token T_NEW "new" T_NOT "!" T_NOT_EQ "!=" +%token T_NOT_EQ_EQ "!==" T_NUMERIC_LITERAL "numeric literal" T_OR "|" +%token T_OR_EQ "|=" T_OR_OR "||" T_PLUS "+" +%token T_PLUS_EQ "+=" T_PLUS_PLUS "++" T_QUESTION "?" +%token T_RBRACE "}" T_RBRACKET "]" T_REMAINDER "%" +%token T_REMAINDER_EQ "%=" T_RETURN "return" T_RPAREN ")" +%token T_SEMICOLON ";" T_AUTOMATIC_SEMICOLON T_STAR "*" +%token T_STAR_EQ "*=" T_STRING_LITERAL "string literal" +%token T_SWITCH "switch" T_THIS "this" T_THROW "throw" +%token T_TILDE "~" T_TRY "try" T_TYPEOF "typeof" +%token T_VAR "var" T_VOID "void" T_WHILE "while" +%token T_WITH "with" T_XOR "^" T_XOR_EQ "^=" +%token T_NULL "null" T_TRUE "true" T_FALSE "false" +%token T_CONST "const" +%token T_DEBUGGER "debugger" +%token T_RESERVED_WORD "reserved word" + +%start Program + +/. +#include "translator.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qnumeric.h> +#include <QtCore/qstring.h> +#include <QtCore/qtextcodec.h> +#include <QtCore/qvariant.h> + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +QT_BEGIN_NAMESPACE + +static void recordMessage( + Translator *tor, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, bool plural, const QString &fileName, int lineNo) +{ + TranslatorMessage msg( + context, text, comment, QString(), + fileName, lineNo, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + tor->replace(msg); +} + + +namespace QScript +{ + +class Lexer +{ +public: + Lexer(); + ~Lexer(); + + void setCode(const QString &c, int lineno); + int lex(); + + int currentLineNo() const { return yylineno; } + int currentColumnNo() const { return yycolumn; } + + int startLineNo() const { return startlineno; } + int startColumnNo() const { return startcolumn; } + + int endLineNo() const { return currentLineNo(); } + int endColumnNo() const + { int col = currentColumnNo(); return (col > 0) ? col - 1 : col; } + + bool prevTerminator() const { return terminator; } + + enum State { Start, + Identifier, + InIdentifier, + InSingleLineComment, + InMultiLineComment, + InNum, + InNum0, + InHex, + InOctal, + InDecimal, + InExponentIndicator, + InExponent, + Hex, + Octal, + Number, + String, + Eof, + InString, + InEscapeSequence, + InHexEscape, + InUnicodeEscape, + Other, + Bad }; + + enum Error { + NoError, + IllegalCharacter, + UnclosedStringLiteral, + IllegalEscapeSequence, + IllegalUnicodeEscapeSequence, + UnclosedComment, + IllegalExponentIndicator, + IllegalIdentifier + }; + + enum ParenthesesState { + IgnoreParentheses, + CountParentheses, + BalancedParentheses + }; + + enum RegExpBodyPrefix { + NoPrefix, + EqualPrefix + }; + + bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); + + QString pattern; + int flags; + + State lexerState() const + { return state; } + + QString errorMessage() const + { return errmsg; } + void setErrorMessage(const QString &err) + { errmsg = err; } + void setErrorMessage(const char *err) + { setErrorMessage(QString::fromLatin1(err)); } + + Error error() const + { return err; } + void clearError() + { err = NoError; } + +private: + int yylineno; + bool done; + char *buffer8; + QChar *buffer16; + uint size8, size16; + uint pos8, pos16; + bool terminator; + bool restrKeyword; + // encountered delimiter like "'" and "}" on last run + bool delimited; + int stackToken; + + State state; + void setDone(State s); + uint pos; + void shift(uint p); + int lookupKeyword(const char *); + + bool isWhiteSpace() const; + bool isLineTerminator() const; + bool isHexDigit(ushort c) const; + bool isOctalDigit(ushort c) const; + + int matchPunctuator(ushort c1, ushort c2, + ushort c3, ushort c4); + ushort singleEscape(ushort c) const; + ushort convertOctal(ushort c1, ushort c2, + ushort c3) const; +public: + static unsigned char convertHex(ushort c1); + static unsigned char convertHex(ushort c1, ushort c2); + static QChar convertUnicode(ushort c1, ushort c2, + ushort c3, ushort c4); + static bool isIdentLetter(ushort c); + static bool isDecimalDigit(ushort c); + + inline int ival() const { return qsyylval.toInt(); } + inline double dval() const { return qsyylval.toDouble(); } + inline QString ustr() const { return qsyylval.toString(); } + inline QVariant val() const { return qsyylval; } + + const QChar *characterBuffer() const { return buffer16; } + int characterCount() const { return pos16; } + +private: + void record8(ushort c); + void record16(QChar c); + void recordStartPos(); + + int findReservedWord(const QChar *buffer, int size) const; + + void syncProhibitAutomaticSemicolon(); + + const QChar *code; + uint length; + int yycolumn; + int startlineno; + int startcolumn; + int bol; // begin of line + + QVariant qsyylval; + + // current and following unicode characters + ushort current, next1, next2, next3; + + struct keyword { + const char *name; + int token; + }; + + QString errmsg; + Error err; + + bool wantRx; + bool check_reserved; + + ParenthesesState parenthesesState; + int parenthesesCount; + bool prohibitAutomaticSemicolon; +}; + +} // namespace QScript + +extern double qstrtod(const char *s00, char const **se, bool *ok); + +#define shiftWindowsLineBreak() if(current == '\r' && next1 == '\n') shift(1); + +namespace QScript { + +static int toDigit(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'a') && (c <= 'z')) + return 10 + c - 'a'; + else if ((c >= 'A') && (c <= 'Z')) + return 10 + c - 'A'; + return -1; +} + +double integerFromString(const char *buf, int size, int radix) +{ + if (size == 0) + return qSNaN(); + + double sign = 1.0; + int i = 0; + if (buf[0] == '+') { + ++i; + } else if (buf[0] == '-') { + sign = -1.0; + ++i; + } + + if (((size-i) >= 2) && (buf[i] == '0')) { + if (((buf[i+1] == 'x') || (buf[i+1] == 'X')) + && (radix < 34)) { + if ((radix != 0) && (radix != 16)) + return 0; + radix = 16; + i += 2; + } else { + if (radix == 0) { + radix = 8; + ++i; + } + } + } else if (radix == 0) { + radix = 10; + } + + int j = i; + for ( ; i < size; ++i) { + int d = toDigit(buf[i]); + if ((d == -1) || (d >= radix)) + break; + } + double result; + if (j == i) { + if (!qstrcmp(buf, "Infinity")) + result = qInf(); + else + result = qSNaN(); + } else { + result = 0; + double multiplier = 1; + for (--i ; i >= j; --i, multiplier *= radix) + result += toDigit(buf[i]) * multiplier; + } + result *= sign; + return result; +} + +} // namespace QScript + +QScript::Lexer::Lexer() + : + yylineno(0), + size8(128), size16(128), restrKeyword(false), + stackToken(-1), pos(0), + code(0), length(0), + bol(true), + current(0), next1(0), next2(0), next3(0), + err(NoError), + check_reserved(true), + parenthesesState(IgnoreParentheses), + prohibitAutomaticSemicolon(false) +{ + // allocate space for read buffers + buffer8 = new char[size8]; + buffer16 = new QChar[size16]; + flags = 0; + +} + +QScript::Lexer::~Lexer() +{ + delete [] buffer8; + delete [] buffer16; +} + +void QScript::Lexer::setCode(const QString &c, int lineno) +{ + errmsg = QString(); + yylineno = lineno; + yycolumn = 1; + restrKeyword = false; + delimited = false; + stackToken = -1; + pos = 0; + code = c.unicode(); + length = c.length(); + bol = true; + + // read first characters + current = (length > 0) ? code[0].unicode() : 0; + next1 = (length > 1) ? code[1].unicode() : 0; + next2 = (length > 2) ? code[2].unicode() : 0; + next3 = (length > 3) ? code[3].unicode() : 0; +} + +void QScript::Lexer::shift(uint p) +{ + while (p--) { + ++pos; + ++yycolumn; + current = next1; + next1 = next2; + next2 = next3; + next3 = (pos + 3 < length) ? code[pos+3].unicode() : 0; + } +} + +void QScript::Lexer::setDone(State s) +{ + state = s; + done = true; +} + +int QScript::Lexer::findReservedWord(const QChar *c, int size) const +{ + switch (size) { + case 2: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('o')) + return QScriptGrammar::T_DO; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('f')) + return QScriptGrammar::T_IF; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n')) + return QScriptGrammar::T_IN; + } break; + + case 3: { + if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('o') && c[2] == QLatin1Char('r')) + return QScriptGrammar::T_FOR; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('e') && c[2] == QLatin1Char('w')) + return QScriptGrammar::T_NEW; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') && c[2] == QLatin1Char('y')) + return QScriptGrammar::T_TRY; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('a') && c[2] == QLatin1Char('r')) + return QScriptGrammar::T_VAR; + else if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') && c[2] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 4: { + if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_CASE; + else if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_ELSE; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('s')) + return QScriptGrammar::T_THIS; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('d')) + return QScriptGrammar::T_VOID; + else if (c[0] == QLatin1Char('w') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('h')) + return QScriptGrammar::T_WITH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_TRUE; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('l')) + return QScriptGrammar::T_NULL; + else if (check_reserved) { + if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('m')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('l') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('g')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('r')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('g') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('o')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 5: { + if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('e') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('k')) + return QScriptGrammar::T_BREAK; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('h')) + return QScriptGrammar::T_CATCH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('r') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('w')) + return QScriptGrammar::T_THROW; + else if (c[0] == QLatin1Char('w') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e')) + return QScriptGrammar::T_WHILE; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_CONST; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('e')) + return QScriptGrammar::T_FALSE; + else if (check_reserved) { + if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('r') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('r')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('l')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('s') + && c[4] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('l') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 6: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_DELETE; + else if (c[0] == QLatin1Char('r') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('u') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('n')) + return QScriptGrammar::T_RETURN; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('w') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('c') && c[5] == QLatin1Char('h')) + return QScriptGrammar::T_SWITCH; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('o') && c[5] == QLatin1Char('f')) + return QScriptGrammar::T_TYPEOF; + else if (check_reserved) { + if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('x') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('t') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('c')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('u') && c[3] == QLatin1Char('b') + && c[4] == QLatin1Char('l') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('m') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('b') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('c')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('n') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('i') + && c[4] == QLatin1Char('v') && c[5] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('h') + && c[2] == QLatin1Char('r') && c[3] == QLatin1Char('o') + && c[4] == QLatin1Char('w') && c[5] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 7: { + if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('f') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('u') && c[5] == QLatin1Char('l') + && c[6] == QLatin1Char('t')) + return QScriptGrammar::T_DEFAULT; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('i') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('l') && c[5] == QLatin1Char('l') + && c[6] == QLatin1Char('y')) + return QScriptGrammar::T_FINALLY; + else if (check_reserved) { + if (c[0] == QLatin1Char('b') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('a') + && c[6] == QLatin1Char('n')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('e') && c[1] == QLatin1Char('x') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('n') && c[5] == QLatin1Char('d') + && c[6] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('a') + && c[2] == QLatin1Char('c') && c[3] == QLatin1Char('k') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('g') + && c[6] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('i') && c[3] == QLatin1Char('v') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('t') + && c[6] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 8: { + if (c[0] == QLatin1Char('c') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('i') && c[5] == QLatin1Char('n') + && c[6] == QLatin1Char('u') && c[7] == QLatin1Char('e')) + return QScriptGrammar::T_CONTINUE; + else if (c[0] == QLatin1Char('f') && c[1] == QLatin1Char('u') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('o') && c[7] == QLatin1Char('n')) + return QScriptGrammar::T_FUNCTION; + else if (c[0] == QLatin1Char('d') && c[1] == QLatin1Char('e') + && c[2] == QLatin1Char('b') && c[3] == QLatin1Char('u') + && c[4] == QLatin1Char('g') && c[5] == QLatin1Char('g') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('r')) + return QScriptGrammar::T_DEBUGGER; + else if (check_reserved) { + if (c[0] == QLatin1Char('a') && c[1] == QLatin1Char('b') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('a') + && c[6] == QLatin1Char('c') && c[7] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('v') && c[1] == QLatin1Char('o') + && c[2] == QLatin1Char('l') && c[3] == QLatin1Char('a') + && c[4] == QLatin1Char('t') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('l') && c[7] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 9: { + if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('t') && c[3] == QLatin1Char('e') + && c[4] == QLatin1Char('r') && c[5] == QLatin1Char('f') + && c[6] == QLatin1Char('a') && c[7] == QLatin1Char('c') + && c[8] == QLatin1Char('e')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('t') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('a') && c[3] == QLatin1Char('n') + && c[4] == QLatin1Char('s') && c[5] == QLatin1Char('i') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('t')) + return QScriptGrammar::T_RESERVED_WORD; + else if (c[0] == QLatin1Char('p') && c[1] == QLatin1Char('r') + && c[2] == QLatin1Char('o') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('c') + && c[6] == QLatin1Char('t') && c[7] == QLatin1Char('e') + && c[8] == QLatin1Char('d')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 10: { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('n') + && c[2] == QLatin1Char('s') && c[3] == QLatin1Char('t') + && c[4] == QLatin1Char('a') && c[5] == QLatin1Char('n') + && c[6] == QLatin1Char('c') && c[7] == QLatin1Char('e') + && c[8] == QLatin1Char('o') && c[9] == QLatin1Char('f')) + return QScriptGrammar::T_INSTANCEOF; + else if (check_reserved) { + if (c[0] == QLatin1Char('i') && c[1] == QLatin1Char('m') + && c[2] == QLatin1Char('p') && c[3] == QLatin1Char('l') + && c[4] == QLatin1Char('e') && c[5] == QLatin1Char('m') + && c[6] == QLatin1Char('e') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('t') && c[9] == QLatin1Char('s')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + case 12: { + if (check_reserved) { + if (c[0] == QLatin1Char('s') && c[1] == QLatin1Char('y') + && c[2] == QLatin1Char('n') && c[3] == QLatin1Char('c') + && c[4] == QLatin1Char('h') && c[5] == QLatin1Char('r') + && c[6] == QLatin1Char('o') && c[7] == QLatin1Char('n') + && c[8] == QLatin1Char('i') && c[9] == QLatin1Char('z') + && c[10] == QLatin1Char('e') && c[11] == QLatin1Char('d')) + return QScriptGrammar::T_RESERVED_WORD; + } + } break; + + } // switch + + return -1; +} + +int QScript::Lexer::lex() +{ + int token = 0; + state = Start; + ushort stringType = 0; // either single or double quotes + pos8 = pos16 = 0; + done = false; + terminator = false; + + // did we push a token on the stack previously ? + // (after an automatic semicolon insertion) + if (stackToken >= 0) { + setDone(Other); + token = stackToken; + stackToken = -1; + } + + while (!done) { + switch (state) { + case Start: + if (isWhiteSpace()) { + // do nothing + } else if (current == '/' && next1 == '/') { + recordStartPos(); + shift(1); + state = InSingleLineComment; + } else if (current == '/' && next1 == '*') { + recordStartPos(); + shift(1); + state = InMultiLineComment; + } else if (current == 0) { + syncProhibitAutomaticSemicolon(); + if (!terminator && !delimited && !prohibitAutomaticSemicolon) { + // automatic semicolon insertion if program incomplete + token = QScriptGrammar::T_SEMICOLON; + stackToken = 0; + setDone(Other); + } else { + setDone(Eof); + } + } else if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + yycolumn = 0; + bol = true; + terminator = true; + syncProhibitAutomaticSemicolon(); + if (restrKeyword) { + token = QScriptGrammar::T_SEMICOLON; + setDone(Other); + } + } else if (current == '"' || current == '\'') { + recordStartPos(); + state = InString; + stringType = current; + } else if (isIdentLetter(current)) { + recordStartPos(); + record16(current); + state = InIdentifier; + } else if (current == '0') { + recordStartPos(); + record8(current); + state = InNum0; + } else if (isDecimalDigit(current)) { + recordStartPos(); + record8(current); + state = InNum; + } else if (current == '.' && isDecimalDigit(next1)) { + recordStartPos(); + record8(current); + state = InDecimal; + } else { + recordStartPos(); + token = matchPunctuator(current, next1, next2, next3); + if (token != -1) { + if (terminator && !delimited && !prohibitAutomaticSemicolon + && (token == QScriptGrammar::T_PLUS_PLUS + || token == QScriptGrammar::T_MINUS_MINUS)) { + // automatic semicolon insertion + stackToken = token; + token = QScriptGrammar::T_SEMICOLON; + } + setDone(Other); + } + else { + setDone(Bad); + err = IllegalCharacter; + errmsg = QLatin1String("Illegal character"); + } + } + break; + case InString: + if (current == stringType) { + shift(1); + setDone(String); + } else if (current == 0 || isLineTerminator()) { + setDone(Bad); + err = UnclosedStringLiteral; + errmsg = QLatin1String("Unclosed string at end of line"); + } else if (current == '\\') { + state = InEscapeSequence; + } else { + record16(current); + } + break; + // Escape Sequences inside of strings + case InEscapeSequence: + if (isOctalDigit(current)) { + if (current >= '0' && current <= '3' && + isOctalDigit(next1) && isOctalDigit(next2)) { + record16(convertOctal(current, next1, next2)); + shift(2); + state = InString; + } else if (isOctalDigit(current) && + isOctalDigit(next1)) { + record16(convertOctal('0', current, next1)); + shift(1); + state = InString; + } else if (isOctalDigit(current)) { + record16(convertOctal('0', '0', current)); + state = InString; + } else { + setDone(Bad); + err = IllegalEscapeSequence; + errmsg = QLatin1String("Illegal escape squence"); + } + } else if (current == 'x') + state = InHexEscape; + else if (current == 'u') + state = InUnicodeEscape; + else { + record16(singleEscape(current)); + state = InString; + } + break; + case InHexEscape: + if (isHexDigit(current) && isHexDigit(next1)) { + state = InString; + record16(QLatin1Char(convertHex(current, next1))); + shift(1); + } else if (current == stringType) { + record16(QLatin1Char('x')); + shift(1); + setDone(String); + } else { + record16(QLatin1Char('x')); + record16(current); + state = InString; + } + break; + case InUnicodeEscape: + if (isHexDigit(current) && isHexDigit(next1) && + isHexDigit(next2) && isHexDigit(next3)) { + record16(convertUnicode(current, next1, next2, next3)); + shift(3); + state = InString; + } else if (current == stringType) { + record16(QLatin1Char('u')); + shift(1); + setDone(String); + } else { + setDone(Bad); + err = IllegalUnicodeEscapeSequence; + errmsg = QLatin1String("Illegal unicode escape sequence"); + } + break; + case InSingleLineComment: + if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + yycolumn = 0; + terminator = true; + bol = true; + if (restrKeyword) { + token = QScriptGrammar::T_SEMICOLON; + setDone(Other); + } else + state = Start; + } else if (current == 0) { + setDone(Eof); + } + break; + case InMultiLineComment: + if (current == 0) { + setDone(Bad); + err = UnclosedComment; + errmsg = QLatin1String("Unclosed comment at end of file"); + } else if (isLineTerminator()) { + shiftWindowsLineBreak(); + yylineno++; + } else if (current == '*' && next1 == '/') { + state = Start; + shift(1); + } + break; + case InIdentifier: + if (isIdentLetter(current) || isDecimalDigit(current)) { + record16(current); + break; + } + setDone(Identifier); + break; + case InNum0: + if (current == 'x' || current == 'X') { + record8(current); + state = InHex; + } else if (current == '.') { + record8(current); + state = InDecimal; + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else if (isOctalDigit(current)) { + record8(current); + state = InOctal; + } else if (isDecimalDigit(current)) { + record8(current); + state = InDecimal; + } else { + setDone(Number); + } + break; + case InHex: + if (isHexDigit(current)) + record8(current); + else + setDone(Hex); + break; + case InOctal: + if (isOctalDigit(current)) { + record8(current); + } else if (isDecimalDigit(current)) { + record8(current); + state = InDecimal; + } else { + setDone(Octal); + } + break; + case InNum: + if (isDecimalDigit(current)) { + record8(current); + } else if (current == '.') { + record8(current); + state = InDecimal; + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else { + setDone(Number); + } + break; + case InDecimal: + if (isDecimalDigit(current)) { + record8(current); + } else if (current == 'e' || current == 'E') { + record8(current); + state = InExponentIndicator; + } else { + setDone(Number); + } + break; + case InExponentIndicator: + if (current == '+' || current == '-') { + record8(current); + } else if (isDecimalDigit(current)) { + record8(current); + state = InExponent; + } else { + setDone(Bad); + err = IllegalExponentIndicator; + errmsg = QLatin1String("Illegal syntax for exponential number"); + } + break; + case InExponent: + if (isDecimalDigit(current)) { + record8(current); + } else { + setDone(Number); + } + break; + default: + Q_ASSERT_X(0, "Lexer::lex", "Unhandled state in switch statement"); + } + + // move on to the next character + if (!done) + shift(1); + if (state != Start && state != InSingleLineComment) + bol = false; + } + + // no identifiers allowed directly after numeric literal, e.g. "3in" is bad + if ((state == Number || state == Octal || state == Hex) + && isIdentLetter(current)) { + state = Bad; + err = IllegalIdentifier; + errmsg = QLatin1String("Identifier cannot start with numeric literal"); + } + + // terminate string + buffer8[pos8] = '\0'; + + double dval = 0; + if (state == Number) { + dval = qstrtod(buffer8, 0, 0); + } else if (state == Hex) { // scan hex numbers + dval = QScript::integerFromString(buffer8, pos8, 16); + state = Number; + } else if (state == Octal) { // scan octal number + dval = QScript::integerFromString(buffer8, pos8, 8); + state = Number; + } + + restrKeyword = false; + delimited = false; + + switch (parenthesesState) { + case IgnoreParentheses: + break; + case CountParentheses: + if (token == QScriptGrammar::T_RPAREN) { + --parenthesesCount; + if (parenthesesCount == 0) + parenthesesState = BalancedParentheses; + } else if (token == QScriptGrammar::T_LPAREN) { + ++parenthesesCount; + } + break; + case BalancedParentheses: + parenthesesState = IgnoreParentheses; + break; + } + + switch (state) { + case Eof: + return 0; + case Other: + if(token == QScriptGrammar::T_RBRACE || token == QScriptGrammar::T_SEMICOLON) + delimited = true; + return token; + case Identifier: + if ((token = findReservedWord(buffer16, pos16)) < 0) { + /* TODO: close leak on parse error. same holds true for String */ + qsyylval = QString(buffer16, pos16); + return QScriptGrammar::T_IDENTIFIER; + } + if (token == QScriptGrammar::T_CONTINUE || token == QScriptGrammar::T_BREAK + || token == QScriptGrammar::T_RETURN || token == QScriptGrammar::T_THROW) { + restrKeyword = true; + } else if (token == QScriptGrammar::T_IF || token == QScriptGrammar::T_FOR + || token == QScriptGrammar::T_WHILE || token == QScriptGrammar::T_WITH) { + parenthesesState = CountParentheses; + parenthesesCount = 0; + } else if (token == QScriptGrammar::T_DO) { + parenthesesState = BalancedParentheses; + } + return token; + case String: + qsyylval = QString(buffer16, pos16); + return QScriptGrammar::T_STRING_LITERAL; + case Number: + qsyylval = dval; + return QScriptGrammar::T_NUMERIC_LITERAL; + case Bad: + return -1; + default: + Q_ASSERT(!"unhandled numeration value in switch"); + return -1; + } +} + +bool QScript::Lexer::isWhiteSpace() const +{ + return (current == ' ' || current == '\t' || + current == 0x0b || current == 0x0c); +} + +bool QScript::Lexer::isLineTerminator() const +{ + return (current == '\n' || current == '\r'); +} + +bool QScript::Lexer::isIdentLetter(ushort c) +{ + /* TODO: allow other legitimate unicode chars */ + return ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '$' + || c == '_'); +} + +bool QScript::Lexer::isDecimalDigit(ushort c) +{ + return (c >= '0' && c <= '9'); +} + +bool QScript::Lexer::isHexDigit(ushort c) const +{ + return ((c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F')); +} + +bool QScript::Lexer::isOctalDigit(ushort c) const +{ + return (c >= '0' && c <= '7'); +} + +int QScript::Lexer::matchPunctuator(ushort c1, ushort c2, + ushort c3, ushort c4) +{ + if (c1 == '>' && c2 == '>' && c3 == '>' && c4 == '=') { + shift(4); + return QScriptGrammar::T_GT_GT_GT_EQ; + } else if (c1 == '=' && c2 == '=' && c3 == '=') { + shift(3); + return QScriptGrammar::T_EQ_EQ_EQ; + } else if (c1 == '!' && c2 == '=' && c3 == '=') { + shift(3); + return QScriptGrammar::T_NOT_EQ_EQ; + } else if (c1 == '>' && c2 == '>' && c3 == '>') { + shift(3); + return QScriptGrammar::T_GT_GT_GT; + } else if (c1 == '<' && c2 == '<' && c3 == '=') { + shift(3); + return QScriptGrammar::T_LT_LT_EQ; + } else if (c1 == '>' && c2 == '>' && c3 == '=') { + shift(3); + return QScriptGrammar::T_GT_GT_EQ; + } else if (c1 == '<' && c2 == '=') { + shift(2); + return QScriptGrammar::T_LE; + } else if (c1 == '>' && c2 == '=') { + shift(2); + return QScriptGrammar::T_GE; + } else if (c1 == '!' && c2 == '=') { + shift(2); + return QScriptGrammar::T_NOT_EQ; + } else if (c1 == '+' && c2 == '+') { + shift(2); + return QScriptGrammar::T_PLUS_PLUS; + } else if (c1 == '-' && c2 == '-') { + shift(2); + return QScriptGrammar::T_MINUS_MINUS; + } else if (c1 == '=' && c2 == '=') { + shift(2); + return QScriptGrammar::T_EQ_EQ; + } else if (c1 == '+' && c2 == '=') { + shift(2); + return QScriptGrammar::T_PLUS_EQ; + } else if (c1 == '-' && c2 == '=') { + shift(2); + return QScriptGrammar::T_MINUS_EQ; + } else if (c1 == '*' && c2 == '=') { + shift(2); + return QScriptGrammar::T_STAR_EQ; + } else if (c1 == '/' && c2 == '=') { + shift(2); + return QScriptGrammar::T_DIVIDE_EQ; + } else if (c1 == '&' && c2 == '=') { + shift(2); + return QScriptGrammar::T_AND_EQ; + } else if (c1 == '^' && c2 == '=') { + shift(2); + return QScriptGrammar::T_XOR_EQ; + } else if (c1 == '%' && c2 == '=') { + shift(2); + return QScriptGrammar::T_REMAINDER_EQ; + } else if (c1 == '|' && c2 == '=') { + shift(2); + return QScriptGrammar::T_OR_EQ; + } else if (c1 == '<' && c2 == '<') { + shift(2); + return QScriptGrammar::T_LT_LT; + } else if (c1 == '>' && c2 == '>') { + shift(2); + return QScriptGrammar::T_GT_GT; + } else if (c1 == '&' && c2 == '&') { + shift(2); + return QScriptGrammar::T_AND_AND; + } else if (c1 == '|' && c2 == '|') { + shift(2); + return QScriptGrammar::T_OR_OR; + } + + switch(c1) { + case '=': shift(1); return QScriptGrammar::T_EQ; + case '>': shift(1); return QScriptGrammar::T_GT; + case '<': shift(1); return QScriptGrammar::T_LT; + case ',': shift(1); return QScriptGrammar::T_COMMA; + case '!': shift(1); return QScriptGrammar::T_NOT; + case '~': shift(1); return QScriptGrammar::T_TILDE; + case '?': shift(1); return QScriptGrammar::T_QUESTION; + case ':': shift(1); return QScriptGrammar::T_COLON; + case '.': shift(1); return QScriptGrammar::T_DOT; + case '+': shift(1); return QScriptGrammar::T_PLUS; + case '-': shift(1); return QScriptGrammar::T_MINUS; + case '*': shift(1); return QScriptGrammar::T_STAR; + case '/': shift(1); return QScriptGrammar::T_DIVIDE_; + case '&': shift(1); return QScriptGrammar::T_AND; + case '|': shift(1); return QScriptGrammar::T_OR; + case '^': shift(1); return QScriptGrammar::T_XOR; + case '%': shift(1); return QScriptGrammar::T_REMAINDER; + case '(': shift(1); return QScriptGrammar::T_LPAREN; + case ')': shift(1); return QScriptGrammar::T_RPAREN; + case '{': shift(1); return QScriptGrammar::T_LBRACE; + case '}': shift(1); return QScriptGrammar::T_RBRACE; + case '[': shift(1); return QScriptGrammar::T_LBRACKET; + case ']': shift(1); return QScriptGrammar::T_RBRACKET; + case ';': shift(1); return QScriptGrammar::T_SEMICOLON; + + default: return -1; + } +} + +ushort QScript::Lexer::singleEscape(ushort c) const +{ + switch(c) { + case 'b': + return 0x08; + case 't': + return 0x09; + case 'n': + return 0x0A; + case 'v': + return 0x0B; + case 'f': + return 0x0C; + case 'r': + return 0x0D; + case '"': + return 0x22; + case '\'': + return 0x27; + case '\\': + return 0x5C; + default: + return c; + } +} + +ushort QScript::Lexer::convertOctal(ushort c1, ushort c2, + ushort c3) const +{ + return ((c1 - '0') * 64 + (c2 - '0') * 8 + c3 - '0'); +} + +unsigned char QScript::Lexer::convertHex(ushort c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (c - 'A' + 10); +} + +unsigned char QScript::Lexer::convertHex(ushort c1, ushort c2) +{ + return ((convertHex(c1) << 4) + convertHex(c2)); +} + +QChar QScript::Lexer::convertUnicode(ushort c1, ushort c2, + ushort c3, ushort c4) +{ + return QChar((convertHex(c3) << 4) + convertHex(c4), + (convertHex(c1) << 4) + convertHex(c2)); +} + +void QScript::Lexer::record8(ushort c) +{ + Q_ASSERT(c <= 0xff); + + // enlarge buffer if full + if (pos8 >= size8 - 1) { + char *tmp = new char[2 * size8]; + memcpy(tmp, buffer8, size8 * sizeof(char)); + delete [] buffer8; + buffer8 = tmp; + size8 *= 2; + } + + buffer8[pos8++] = (char) c; +} + +void QScript::Lexer::record16(QChar c) +{ + // enlarge buffer if full + if (pos16 >= size16 - 1) { + QChar *tmp = new QChar[2 * size16]; + memcpy(tmp, buffer16, size16 * sizeof(QChar)); + delete [] buffer16; + buffer16 = tmp; + size16 *= 2; + } + + buffer16[pos16++] = c; +} + +void QScript::Lexer::recordStartPos() +{ + startlineno = yylineno; + startcolumn = yycolumn; +} + +bool QScript::Lexer::scanRegExp(RegExpBodyPrefix prefix) +{ + pos16 = 0; + bool lastWasEscape = false; + + if (prefix == EqualPrefix) + record16(QLatin1Char('=')); + + while (1) { + if (isLineTerminator() || current == 0) { + errmsg = QLatin1String("Unterminated regular expression literal"); + return false; + } + else if (current != '/' || lastWasEscape == true) + { + record16(current); + lastWasEscape = !lastWasEscape && (current == '\\'); + } + else { + pattern = QString(buffer16, pos16); + pos16 = 0; + shift(1); + break; + } + shift(1); + } + + flags = 0; + while (isIdentLetter(current)) { + record16(current); + shift(1); + } + + return true; +} + +void QScript::Lexer::syncProhibitAutomaticSemicolon() +{ + if (parenthesesState == BalancedParentheses) { + // we have seen something like "if (foo)", which means we should + // never insert an automatic semicolon at this point, since it would + // then be expanded into an empty statement (ECMA-262 7.9.1) + prohibitAutomaticSemicolon = true; + parenthesesState = IgnoreParentheses; + } else { + prohibitAutomaticSemicolon = false; + } +} + + +class Translator; + +class QScriptParser: protected $table +{ +public: + QVariant val; + + struct Location { + int startLine; + int startColumn; + int endLine; + int endColumn; + }; + +public: + QScriptParser(); + ~QScriptParser(); + + bool parse(QScript::Lexer *lexer, + const QString &fileName, + Translator *translator); + + inline QString errorMessage() const + { return error_message; } + inline int errorLineNumber() const + { return error_lineno; } + inline int errorColumnNumber() const + { return error_column; } + +protected: + inline void reallocateStack(); + + inline QVariant &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline Location &loc(int index) + { return location_stack [tos + index - 2]; } + +protected: + int tos; + int stack_size; + QVector<QVariant> sym_stack; + int *state_stack; + Location *location_stack; + QString error_message; + int error_lineno; + int error_column; +}; + +inline void QScriptParser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack.resize(stack_size); + state_stack = reinterpret_cast<int*> (qRealloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast<Location*> (qRealloc(location_stack, stack_size * sizeof(Location))); +} + +inline static bool automatic(QScript::Lexer *lexer, int token) +{ + return (token == $table::T_RBRACE) + || (token == 0) + || lexer->prevTerminator(); +} + +QScriptParser::QScriptParser(): + tos(0), + stack_size(0), + sym_stack(0), + state_stack(0), + location_stack(0) +{ +} + +QScriptParser::~QScriptParser() +{ + if (stack_size) { + qFree(state_stack); + qFree(location_stack); + } +} + +static inline QScriptParser::Location location(QScript::Lexer *lexer) +{ + QScriptParser::Location loc; + loc.startLine = lexer->startLineNo(); + loc.startColumn = lexer->startColumnNo(); + loc.endLine = lexer->endLineNo(); + loc.endColumn = lexer->endColumnNo(); + return loc; +} + +bool QScriptParser::parse(QScript::Lexer *lexer, + const QString &fileName, + Translator *translator) +{ + const int INITIAL_STATE = 0; + + int yytoken = -1; + int saved_yytoken = -1; + int identLineNo = -1; + + reallocateStack(); + + tos = 0; + state_stack[++tos] = INITIAL_STATE; + + while (true) + { + const int state = state_stack [tos]; + if (yytoken == -1 && - TERMINAL_COUNT != action_index [state]) + { + if (saved_yytoken == -1) + { + yytoken = lexer->lex(); + location_stack [tos] = location(lexer); + } + else + { + yytoken = saved_yytoken; + saved_yytoken = -1; + } + } + + int act = t_action (state, yytoken); + + if (act == ACCEPT_STATE) + return true; + + else if (act > 0) + { + if (++tos == stack_size) + reallocateStack(); + + sym_stack [tos] = lexer->val (); + state_stack [tos] = act; + location_stack [tos] = location(lexer); + yytoken = -1; + } + + else if (act < 0) + { + int r = - act - 1; + + tos -= rhs [r]; + act = state_stack [tos++]; + + switch (r) { +./ + +PrimaryExpression: T_THIS ; + +PrimaryExpression: T_IDENTIFIER ; +/. +case $rule_number: { + sym(1) = sym(1).toByteArray(); + identLineNo = lexer->startLineNo(); +} break; +./ + +PrimaryExpression: T_NULL ; +PrimaryExpression: T_TRUE ; +PrimaryExpression: T_FALSE ; +PrimaryExpression: T_NUMERIC_LITERAL ; +PrimaryExpression: T_STRING_LITERAL ; + +PrimaryExpression: T_DIVIDE_ ; +/: +#define Q_SCRIPT_REGEXPLITERAL_RULE1 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(QScript::Lexer::NoPrefix); + if (!rx) { + error_message = lexer->errorMessage(); + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + return false; + } +} break; +./ + +PrimaryExpression: T_DIVIDE_EQ ; +/: +#define Q_SCRIPT_REGEXPLITERAL_RULE2 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(QScript::Lexer::EqualPrefix); + if (!rx) { + error_message = lexer->errorMessage(); + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + return false; + } +} break; +./ + +PrimaryExpression: T_LBRACKET ElisionOpt T_RBRACKET ; +PrimaryExpression: T_LBRACKET ElementList T_RBRACKET ; +PrimaryExpression: T_LBRACKET ElementList T_COMMA ElisionOpt T_RBRACKET ; +PrimaryExpression: T_LBRACE PropertyNameAndValueListOpt T_RBRACE ; +PrimaryExpression: T_LPAREN Expression T_RPAREN ; +ElementList: ElisionOpt AssignmentExpression ; +ElementList: ElementList T_COMMA ElisionOpt AssignmentExpression ; +Elision: T_COMMA ; +Elision: Elision T_COMMA ; +ElisionOpt: ; +ElisionOpt: Elision ; +PropertyNameAndValueList: PropertyName T_COLON AssignmentExpression ; +PropertyNameAndValueList: PropertyNameAndValueList T_COMMA PropertyName T_COLON AssignmentExpression ; +PropertyName: T_IDENTIFIER ; +PropertyName: T_STRING_LITERAL ; +PropertyName: T_NUMERIC_LITERAL ; +PropertyName: ReservedIdentifier ; +ReservedIdentifier: T_BREAK ; +ReservedIdentifier: T_CASE ; +ReservedIdentifier: T_CATCH ; +ReservedIdentifier: T_CONST ; +ReservedIdentifier: T_CONTINUE ; +ReservedIdentifier: T_DEBUGGER ; +ReservedIdentifier: T_DEFAULT ; +ReservedIdentifier: T_DELETE ; +ReservedIdentifier: T_DO ; +ReservedIdentifier: T_ELSE ; +ReservedIdentifier: T_FALSE ; +ReservedIdentifier: T_FINALLY ; +ReservedIdentifier: T_FOR ; +ReservedIdentifier: T_FUNCTION ; +ReservedIdentifier: T_IF ; +ReservedIdentifier: T_IN ; +ReservedIdentifier: T_INSTANCEOF ; +ReservedIdentifier: T_NEW ; +ReservedIdentifier: T_NULL ; +ReservedIdentifier: T_RESERVED_WORD ; +ReservedIdentifier: T_RETURN ; +ReservedIdentifier: T_SWITCH ; +ReservedIdentifier: T_THIS ; +ReservedIdentifier: T_THROW ; +ReservedIdentifier: T_TRUE ; +ReservedIdentifier: T_TRY ; +ReservedIdentifier: T_TYPEOF ; +ReservedIdentifier: T_VAR ; +ReservedIdentifier: T_VOID ; +ReservedIdentifier: T_WHILE ; +ReservedIdentifier: T_WITH ; +PropertyIdentifier: T_IDENTIFIER ; +PropertyIdentifier: ReservedIdentifier ; + +MemberExpression: PrimaryExpression ; +MemberExpression: FunctionExpression ; +MemberExpression: MemberExpression T_LBRACKET Expression T_RBRACKET ; +MemberExpression: MemberExpression T_DOT PropertyIdentifier ; +MemberExpression: T_NEW MemberExpression Arguments ; +NewExpression: MemberExpression ; +NewExpression: T_NEW NewExpression ; + +CallExpression: MemberExpression Arguments ; +/. +case $rule_number: { + QString name = sym(1).toString(); + if ((name == QLatin1String("qsTranslate")) || (name == QLatin1String("QT_TRANSLATE_NOOP"))) { + QVariantList args = sym(2).toList(); + if (args.size() < 2) { + qWarning("%s:%d: %s() requires at least two arguments", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + if ((args.at(0).type() != QVariant::String) + || (args.at(1).type() != QVariant::String)) { + qWarning("%s:%d: %s(): both arguments must be literal strings", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + QString context = args.at(0).toString(); + QString text = args.at(1).toString(); + QString comment = args.value(2).toString(); + QString extracomment; + bool plural = (args.size() > 4); + recordMessage(translator, context, text, comment, extracomment, + plural, fileName, identLineNo); + } + } + } else if ((name == QLatin1String("qsTr")) || (name == QLatin1String("QT_TR_NOOP"))) { + QVariantList args = sym(2).toList(); + if (args.size() < 1) { + qWarning("%s:%d: %s() requires at least one argument", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + if (args.at(0).type() != QVariant::String) { + qWarning("%s:%d: %s(): text to translate must be a literal string", + qPrintable(fileName), identLineNo, qPrintable(name)); + } else { + QString context = QFileInfo(fileName).baseName(); + QString text = args.at(0).toString(); + QString comment = args.value(1).toString(); + QString extracomment; + bool plural = (args.size() > 2); + recordMessage(translator, context, text, comment, extracomment, + plural, fileName, identLineNo); + } + } + } +} break; +./ + +CallExpression: CallExpression Arguments ; +CallExpression: CallExpression T_LBRACKET Expression T_RBRACKET ; +CallExpression: CallExpression T_DOT PropertyIdentifier ; + +Arguments: T_LPAREN T_RPAREN ; +/. +case $rule_number: { + sym(1) = QVariantList(); +} break; +./ + +Arguments: T_LPAREN ArgumentList T_RPAREN ; +/. +case $rule_number: { + sym(1) = sym(2); +} break; +./ + +ArgumentList: AssignmentExpression ; +/. +case $rule_number: { + sym(1) = QVariantList() << sym(1); +} break; +./ + +ArgumentList: ArgumentList T_COMMA AssignmentExpression ; +/. +case $rule_number: { + sym(1) = sym(1).toList() << sym(3); +} break; +./ + +LeftHandSideExpression: NewExpression ; +LeftHandSideExpression: CallExpression ; +PostfixExpression: LeftHandSideExpression ; +PostfixExpression: LeftHandSideExpression T_PLUS_PLUS ; +PostfixExpression: LeftHandSideExpression T_MINUS_MINUS ; +UnaryExpression: PostfixExpression ; +UnaryExpression: T_DELETE UnaryExpression ; +UnaryExpression: T_VOID UnaryExpression ; +UnaryExpression: T_TYPEOF UnaryExpression ; +UnaryExpression: T_PLUS_PLUS UnaryExpression ; +UnaryExpression: T_MINUS_MINUS UnaryExpression ; +UnaryExpression: T_PLUS UnaryExpression ; +UnaryExpression: T_MINUS UnaryExpression ; +UnaryExpression: T_TILDE UnaryExpression ; +UnaryExpression: T_NOT UnaryExpression ; +MultiplicativeExpression: UnaryExpression ; +MultiplicativeExpression: MultiplicativeExpression T_STAR UnaryExpression ; +MultiplicativeExpression: MultiplicativeExpression T_DIVIDE_ UnaryExpression ; +MultiplicativeExpression: MultiplicativeExpression T_REMAINDER UnaryExpression ; +AdditiveExpression: MultiplicativeExpression ; + +AdditiveExpression: AdditiveExpression T_PLUS MultiplicativeExpression ; +/. +case $rule_number: { + if ((sym(1).type() == QVariant::String) || (sym(3).type() == QVariant::String)) + sym(1) = sym(1).toString() + sym(3).toString(); + else + sym(1) = QVariant(); +} break; +./ + +AdditiveExpression: AdditiveExpression T_MINUS MultiplicativeExpression ; +ShiftExpression: AdditiveExpression ; +ShiftExpression: ShiftExpression T_LT_LT AdditiveExpression ; +ShiftExpression: ShiftExpression T_GT_GT AdditiveExpression ; +ShiftExpression: ShiftExpression T_GT_GT_GT AdditiveExpression ; +RelationalExpression: ShiftExpression ; +RelationalExpression: RelationalExpression T_LT ShiftExpression ; +RelationalExpression: RelationalExpression T_GT ShiftExpression ; +RelationalExpression: RelationalExpression T_LE ShiftExpression ; +RelationalExpression: RelationalExpression T_GE ShiftExpression ; +RelationalExpression: RelationalExpression T_INSTANCEOF ShiftExpression ; +RelationalExpression: RelationalExpression T_IN ShiftExpression ; +RelationalExpressionNotIn: ShiftExpression ; +RelationalExpressionNotIn: RelationalExpressionNotIn T_LT ShiftExpression ; +RelationalExpressionNotIn: RelationalExpressionNotIn T_GT ShiftExpression ; +RelationalExpressionNotIn: RelationalExpressionNotIn T_LE ShiftExpression ; +RelationalExpressionNotIn: RelationalExpressionNotIn T_GE ShiftExpression ; +RelationalExpressionNotIn: RelationalExpressionNotIn T_INSTANCEOF ShiftExpression ; +EqualityExpression: RelationalExpression ; +EqualityExpression: EqualityExpression T_EQ_EQ RelationalExpression ; +EqualityExpression: EqualityExpression T_NOT_EQ RelationalExpression ; +EqualityExpression: EqualityExpression T_EQ_EQ_EQ RelationalExpression ; +EqualityExpression: EqualityExpression T_NOT_EQ_EQ RelationalExpression ; +EqualityExpressionNotIn: RelationalExpressionNotIn ; +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ RelationalExpressionNotIn ; +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ RelationalExpressionNotIn; +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ_EQ RelationalExpressionNotIn ; +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ_EQ RelationalExpressionNotIn ; +BitwiseANDExpression: EqualityExpression ; +BitwiseANDExpression: BitwiseANDExpression T_AND EqualityExpression ; +BitwiseANDExpressionNotIn: EqualityExpressionNotIn ; +BitwiseANDExpressionNotIn: BitwiseANDExpressionNotIn T_AND EqualityExpressionNotIn ; +BitwiseXORExpression: BitwiseANDExpression ; +BitwiseXORExpression: BitwiseXORExpression T_XOR BitwiseANDExpression ; +BitwiseXORExpressionNotIn: BitwiseANDExpressionNotIn ; +BitwiseXORExpressionNotIn: BitwiseXORExpressionNotIn T_XOR BitwiseANDExpressionNotIn ; +BitwiseORExpression: BitwiseXORExpression ; +BitwiseORExpression: BitwiseORExpression T_OR BitwiseXORExpression ; +BitwiseORExpressionNotIn: BitwiseXORExpressionNotIn ; +BitwiseORExpressionNotIn: BitwiseORExpressionNotIn T_OR BitwiseXORExpressionNotIn ; +LogicalANDExpression: BitwiseORExpression ; +LogicalANDExpression: LogicalANDExpression T_AND_AND BitwiseORExpression ; +LogicalANDExpressionNotIn: BitwiseORExpressionNotIn ; +LogicalANDExpressionNotIn: LogicalANDExpressionNotIn T_AND_AND BitwiseORExpressionNotIn ; +LogicalORExpression: LogicalANDExpression ; +LogicalORExpression: LogicalORExpression T_OR_OR LogicalANDExpression ; +LogicalORExpressionNotIn: LogicalANDExpressionNotIn ; +LogicalORExpressionNotIn: LogicalORExpressionNotIn T_OR_OR LogicalANDExpressionNotIn ; +ConditionalExpression: LogicalORExpression ; +ConditionalExpression: LogicalORExpression T_QUESTION AssignmentExpression T_COLON AssignmentExpression ; +ConditionalExpressionNotIn: LogicalORExpressionNotIn ; +ConditionalExpressionNotIn: LogicalORExpressionNotIn T_QUESTION AssignmentExpressionNotIn T_COLON AssignmentExpressionNotIn ; +AssignmentExpression: ConditionalExpression ; +AssignmentExpression: LeftHandSideExpression AssignmentOperator AssignmentExpression ; +AssignmentExpressionNotIn: ConditionalExpressionNotIn ; +AssignmentExpressionNotIn: LeftHandSideExpression AssignmentOperator AssignmentExpressionNotIn ; +AssignmentOperator: T_EQ ; +AssignmentOperator: T_STAR_EQ ; +AssignmentOperator: T_DIVIDE_EQ ; +AssignmentOperator: T_REMAINDER_EQ ; +AssignmentOperator: T_PLUS_EQ ; +AssignmentOperator: T_MINUS_EQ ; +AssignmentOperator: T_LT_LT_EQ ; +AssignmentOperator: T_GT_GT_EQ ; +AssignmentOperator: T_GT_GT_GT_EQ ; +AssignmentOperator: T_AND_EQ ; +AssignmentOperator: T_XOR_EQ ; +AssignmentOperator: T_OR_EQ ; +Expression: AssignmentExpression ; +Expression: Expression T_COMMA AssignmentExpression ; +ExpressionOpt: ; +ExpressionOpt: Expression ; +ExpressionNotIn: AssignmentExpressionNotIn ; +ExpressionNotIn: ExpressionNotIn T_COMMA AssignmentExpressionNotIn ; +ExpressionNotInOpt: ; +ExpressionNotInOpt: ExpressionNotIn ; + +Statement: Block ; +Statement: VariableStatement ; +Statement: EmptyStatement ; +Statement: ExpressionStatement ; +Statement: IfStatement ; +Statement: IterationStatement ; +Statement: ContinueStatement ; +Statement: BreakStatement ; +Statement: ReturnStatement ; +Statement: WithStatement ; +Statement: LabelledStatement ; +Statement: SwitchStatement ; +Statement: ThrowStatement ; +Statement: TryStatement ; +Statement: DebuggerStatement ; + +Block: T_LBRACE StatementListOpt T_RBRACE ; +StatementList: Statement ; +StatementList: StatementList Statement ; +StatementListOpt: ; +StatementListOpt: StatementList ; +VariableStatement: VariableDeclarationKind VariableDeclarationList T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +VariableStatement: VariableDeclarationKind VariableDeclarationList T_SEMICOLON ; +VariableDeclarationKind: T_CONST ; +VariableDeclarationKind: T_VAR ; +VariableDeclarationList: VariableDeclaration ; +VariableDeclarationList: VariableDeclarationList T_COMMA VariableDeclaration ; +VariableDeclarationListNotIn: VariableDeclarationNotIn ; +VariableDeclarationListNotIn: VariableDeclarationListNotIn T_COMMA VariableDeclarationNotIn ; +VariableDeclaration: T_IDENTIFIER InitialiserOpt ; +VariableDeclarationNotIn: T_IDENTIFIER InitialiserNotInOpt ; +Initialiser: T_EQ AssignmentExpression ; +InitialiserOpt: ; +InitialiserOpt: Initialiser ; +InitialiserNotIn: T_EQ AssignmentExpressionNotIn ; +InitialiserNotInOpt: ; +InitialiserNotInOpt: InitialiserNotIn ; +EmptyStatement: T_SEMICOLON ; +ExpressionStatement: Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ExpressionStatement: Expression T_SEMICOLON ; +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement T_ELSE Statement ; +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement ; +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_SEMICOLON ; +IterationStatement: T_WHILE T_LPAREN Expression T_RPAREN Statement ; +IterationStatement: T_FOR T_LPAREN ExpressionNotInOpt T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationListNotIn T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +IterationStatement: T_FOR T_LPAREN LeftHandSideExpression T_IN Expression T_RPAREN Statement ; +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationNotIn T_IN Expression T_RPAREN Statement ; +ContinueStatement: T_CONTINUE T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE T_SEMICOLON ; +ContinueStatement: T_CONTINUE T_IDENTIFIER T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE T_IDENTIFIER T_SEMICOLON ; +BreakStatement: T_BREAK T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK T_SEMICOLON ; +BreakStatement: T_BREAK T_IDENTIFIER T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK T_IDENTIFIER T_SEMICOLON ; +ReturnStatement: T_RETURN ExpressionOpt T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ReturnStatement: T_RETURN ExpressionOpt T_SEMICOLON ; +WithStatement: T_WITH T_LPAREN Expression T_RPAREN Statement ; +SwitchStatement: T_SWITCH T_LPAREN Expression T_RPAREN CaseBlock ; +CaseBlock: T_LBRACE CaseClausesOpt T_RBRACE ; +CaseBlock: T_LBRACE CaseClausesOpt DefaultClause CaseClausesOpt T_RBRACE ; +CaseClauses: CaseClause ; +CaseClauses: CaseClauses CaseClause ; +CaseClausesOpt: ; +CaseClausesOpt: CaseClauses ; +CaseClause: T_CASE Expression T_COLON StatementListOpt ; +DefaultClause: T_DEFAULT T_COLON StatementListOpt ; +LabelledStatement: T_IDENTIFIER T_COLON Statement ; +ThrowStatement: T_THROW Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ThrowStatement: T_THROW Expression T_SEMICOLON ; +TryStatement: T_TRY Block Catch ; +TryStatement: T_TRY Block Finally ; +TryStatement: T_TRY Block Catch Finally ; +Catch: T_CATCH T_LPAREN T_IDENTIFIER T_RPAREN Block ; +Finally: T_FINALLY Block ; +DebuggerStatement: T_DEBUGGER ; +FunctionDeclaration: T_FUNCTION T_IDENTIFIER T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +FunctionExpression: T_FUNCTION IdentifierOpt T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +FormalParameterList: T_IDENTIFIER ; +FormalParameterList: FormalParameterList T_COMMA T_IDENTIFIER ; +FormalParameterListOpt: ; +FormalParameterListOpt: FormalParameterList ; +FunctionBodyOpt: ; +FunctionBodyOpt: FunctionBody ; +FunctionBody: SourceElements ; +Program: SourceElements ; +SourceElements: SourceElement ; +SourceElements: SourceElements SourceElement ; +SourceElement: Statement ; +SourceElement: FunctionDeclaration ; +IdentifierOpt: ; +IdentifierOpt: T_IDENTIFIER ; +PropertyNameAndValueListOpt: ; +PropertyNameAndValueListOpt: PropertyNameAndValueList ; + +/. + } // switch + + state_stack [tos] = nt_action (act, lhs [r] - TERMINAL_COUNT); + + if (rhs[r] > 1) { + location_stack[tos - 1].endLine = location_stack[tos + rhs[r] - 2].endLine; + location_stack[tos - 1].endColumn = location_stack[tos + rhs[r] - 2].endColumn; + location_stack[tos] = location_stack[tos + rhs[r] - 1]; + } + } + + else + { + if (saved_yytoken == -1 && automatic (lexer, yytoken) && t_action (state, T_AUTOMATIC_SEMICOLON) > 0) + { + saved_yytoken = yytoken; + yytoken = T_SEMICOLON; + continue; + } + + else if ((state == INITIAL_STATE) && (yytoken == 0)) { + // accept empty input + yytoken = T_SEMICOLON; + continue; + } + + int ers = state; + int shifts = 0; + int reduces = 0; + int expected_tokens [3]; + for (int tk = 0; tk < TERMINAL_COUNT; ++tk) + { + int k = t_action (ers, tk); + + if (! k) + continue; + else if (k < 0) + ++reduces; + else if (spell [tk]) + { + if (shifts < 3) + expected_tokens [shifts] = tk; + ++shifts; + } + } + + error_message.clear (); + if (shifts && shifts < 3) + { + bool first = true; + + for (int s = 0; s < shifts; ++s) + { + if (first) + error_message += QLatin1String ("Expected "); + else + error_message += QLatin1String (", "); + + first = false; + error_message += QLatin1String("`"); + error_message += QLatin1String (spell [expected_tokens [s]]); + error_message += QLatin1String("'"); + } + } + + if (error_message.isEmpty()) + error_message = lexer->errorMessage(); + + error_lineno = lexer->startLineNo(); + error_column = lexer->startColumnNo(); + + return false; + } + } + + return false; +} + + +bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QTextStream ts(&dev); + QByteArray codecName; + if (!cd.m_codecForSource.isEmpty()) + codecName = cd.m_codecForSource; + else + codecName = translator.codecName(); // Just because it should be latin1 already + ts.setCodec(QTextCodec::codecForName(codecName)); + ts.setAutoDetectUnicode(true); + + QString code = ts.readAll(); + QScript::Lexer lexer; + lexer.setCode(code, /*lineNumber=*/1); + QScriptParser parser; + if (!parser.parse(&lexer, cd.m_sourceFileName, &translator)) { + qWarning("%s:%d: %s", qPrintable(cd.m_sourceFileName), parser.errorLineNumber(), + qPrintable(parser.errorMessage())); + return false; + } + + // Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it. + translator.setCodecName("UTF-8"); + return true; +} + +bool saveQScript(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + Q_UNUSED(dev); + Q_UNUSED(translator); + cd.appendError(QLatin1String("Cannot save .js files")); + return false; +} + +int initQScript() +{ + Translator::FileFormat format; + format.extension = QLatin1String("js"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.description = QObject::tr("Qt Script source files"); + format.loader = &loadQScript; + format.saver = &saveQScript; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initQScript) + +QT_END_NAMESPACE +./ diff --git a/tools/linguist/shared/simtexth.cpp b/tools/linguist/shared/simtexth.cpp new file mode 100644 index 0000000..0556ed6 --- /dev/null +++ b/tools/linguist/shared/simtexth.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "simtexth.h" +#include "translator.h" + +#include <QtCore/QByteArray> +#include <QtCore/QString> +#include <QtCore/QList> + + +QT_BEGIN_NAMESPACE + +typedef QList<TranslatorMessage> TML; + +/* + How similar are two texts? The approach used here relies on co-occurrence + matrices and is very efficient. + + Let's see with an example: how similar are "here" and "hither"? The + co-occurrence matrix M for "here" is M[h,e] = 1, M[e,r] = 1, M[r,e] = 1, and 0 + elsewhere; the matrix N for "hither" is N[h,i] = 1, N[i,t] = 1, ..., + N[h,e] = 1, N[e,r] = 1, and 0 elsewhere. The union U of both matrices is the + matrix U[i,j] = max { M[i,j], N[i,j] }, and the intersection V is + V[i,j] = min { M[i,j], N[i,j] }. The score for a pair of texts is + + score = (sum of V[i,j] over all i, j) / (sum of U[i,j] over all i, j), + + a formula suggested by Arnt Gulbrandsen. Here we have + + score = 2 / 6, + + or one third. + + The implementation differs from this in a few details. Most importantly, + repetitions are ignored; for input "xxx", M[x,x] equals 1, not 2. +*/ + +/* + Every character is assigned to one of 20 buckets so that the co-occurrence + matrix requires only 20 * 20 = 400 bits, not 256 * 256 = 65536 bits or even + more if we want the whole Unicode. Which character falls in which bucket is + arbitrary. + + The second half of the table is a replica of the first half, because of + laziness. +*/ +static const int indexOf[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// ! " # $ % & ' ( ) * + , - . / + 0, 2, 6, 7, 10, 12, 15, 19, 2, 6, 7, 10, 12, 15, 19, 0, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 1, 3, 4, 5, 8, 9, 11, 13, 14, 16, 2, 6, 7, 10, 12, 15, +// @ A B C D E F G H I J K L M N O + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 10, 11, 12, 13, 14, +// P Q R S T U V W X Y Z [ \ ] ^ _ + 15, 12, 16, 17, 18, 19, 2, 10, 15, 7, 19, 2, 6, 7, 10, 0, +// ` a b c d e f g h i j k l m n o + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 10, 11, 12, 13, 14, +// p q r s t u v w x y z { | } ~ + 15, 12, 16, 17, 18, 19, 2, 10, 15, 7, 19, 2, 6, 7, 10, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 6, 7, 10, 12, 15, 19, 2, 6, 7, 10, 12, 15, 19, 0, + 1, 3, 4, 5, 8, 9, 11, 13, 14, 16, 2, 6, 7, 10, 12, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 10, 11, 12, 13, 14, + 15, 12, 16, 17, 18, 19, 2, 10, 15, 7, 19, 2, 6, 7, 10, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 10, 11, 12, 13, 14, + 15, 12, 16, 17, 18, 19, 2, 10, 15, 7, 19, 2, 6, 7, 10, 0 +}; + +/* + The entry bitCount[i] (for i between 0 and 255) is the number of bits used to + represent i in binary. +*/ +static const int bitCount[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 +}; + +struct CoMatrix +{ + /* + The matrix has 20 * 20 = 400 entries. This requires 50 bytes, or 13 + words. Some operations are performed on words for more efficiency. + */ + union { + quint8 b[52]; + quint32 w[13]; + }; + + CoMatrix() { memset( b, 0, 52 ); } + + CoMatrix(const QString &str) + { + QByteArray ba = str.toUtf8(); + const char *text = ba.constData(); + char c = '\0', d; + memset( b, 0, 52 ); + /* + The Knuth books are not in the office only for show; they help make + loops 30% faster and 20% as readable. + */ + while ( (d = *text) != '\0' ) { + setCoOccurence( c, d ); + if ( (c = *++text) != '\0' ) { + setCoOccurence( d, c ); + text++; + } + } + } + + void setCoOccurence( char c, char d ) { + int k = indexOf[(uchar) c] + 20 * indexOf[(uchar) d]; + b[k >> 3] |= (1 << (k & 0x7)); + } + + int worth() const { + int w = 0; + for ( int i = 0; i < 50; i++ ) + w += bitCount[b[i]]; + return w; + } +}; + +static inline CoMatrix reunion(const CoMatrix &m, const CoMatrix &n) +{ + CoMatrix p; + for (int i = 0; i < 13; ++i) + p.w[i] = m.w[i] | n.w[i]; + return p; +} + +static inline CoMatrix intersection(const CoMatrix &m, const CoMatrix &n) +{ + CoMatrix p; + for (int i = 0; i < 13; ++i) + p.w[i] = m.w[i] & n.w[i]; + return p; +} + +StringSimilarityMatcher::StringSimilarityMatcher(const QString &stringToMatch) +{ + m_cm = new CoMatrix(stringToMatch); + m_length = stringToMatch.length(); +} + +int StringSimilarityMatcher::getSimilarityScore(const QString &strCandidate) +{ + CoMatrix cmTarget(strCandidate); + int delta = qAbs(m_length - strCandidate.size()); + int score = ( (intersection(*m_cm, cmTarget).worth() + 1) << 10 ) / + ( reunion(*m_cm, cmTarget).worth() + (delta << 1) + 1 ); + return score; +} + +StringSimilarityMatcher::~StringSimilarityMatcher() +{ + delete m_cm; +} + +/** + * Checks how similar two strings are. + * The return value is the score, and a higher score is more similar + * than one with a low score. + * Linguist considers a score over 190 to be a good match. + * \sa StringSimilarityMatcher + */ +int getSimilarityScore(const QString &str1, const QString &str2) +{ + CoMatrix cmTarget(str2); + CoMatrix cm(str1); + int delta = qAbs(str1.size() - str2.size()); + + int score = ( (intersection(cm, cmTarget).worth() + 1) << 10 ) + / ( reunion(cm, cmTarget).worth() + (delta << 1) + 1 ); + + return score; +} + +CandidateList similarTextHeuristicCandidates(const Translator *tor, + const QString &text, int maxCandidates) +{ + QList<int> scores; + CandidateList candidates; + + TML all = tor->translatedMessages(); + + foreach (const TranslatorMessage &mtm, all) { + if (mtm.type() == TranslatorMessage::Unfinished + || mtm.translation().isEmpty()) + continue; + + QString s = mtm.sourceText(); + int score = getSimilarityScore(s, text); + + if (candidates.size() == maxCandidates && score > scores[maxCandidates - 1] ) + candidates.removeLast(); + + if (candidates.size() < maxCandidates && score >= textSimilarityThreshold) { + Candidate cand( s, mtm.translation() ); + + int i; + for (i = 0; i < candidates.size(); i++) { + if (score >= scores.at(i)) { + if (score == scores.at(i)) { + if (candidates.at(i) == cand) + goto continue_outer_loop; + } else { + break; + } + } + } + scores.insert(i, score); + candidates.insert(i, cand); + } + continue_outer_loop: + ; + } + return candidates; +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/simtexth.h b/tools/linguist/shared/simtexth.h new file mode 100644 index 0000000..e3cad91 --- /dev/null +++ b/tools/linguist/shared/simtexth.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SIMTEXTH_H +#define SIMTEXTH_H + +const int textSimilarityThreshold = 190; + +#include <QString> +#include <QList> + +QT_BEGIN_NAMESPACE + +class Translator; + +struct Candidate +{ + Candidate() {} + Candidate(const QString& source0, const QString &target0) + : source(source0), target(target0) + {} + + QString source; + QString target; +}; + +inline bool operator==( const Candidate& c, const Candidate& d ) { + return c.target == d.target && c.source == d.source; +} +inline bool operator!=( const Candidate& c, const Candidate& d ) { + return !operator==( c, d ); +} + +typedef QList<Candidate> CandidateList; + +struct CoMatrix; +/** + * This class is more efficient for searching through a large array of candidate strings, since we only + * have to construct the CoMatrix for the \a stringToMatch once, + * after that we just call getSimilarityScore(strCandidate). + * \sa getSimilarityScore + */ +class StringSimilarityMatcher { +public: + StringSimilarityMatcher(const QString &stringToMatch); + ~StringSimilarityMatcher(); + int getSimilarityScore(const QString &strCandidate); + +private: + CoMatrix *m_cm; + int m_length; +}; + +int getSimilarityScore(const QString &str1, const QString &str2); + +CandidateList similarTextHeuristicCandidates( const Translator *tor, + const QString &text, + int maxCandidates ); + +QT_END_NAMESPACE + +#endif diff --git a/tools/linguist/shared/translator.cpp b/tools/linguist/shared/translator.cpp new file mode 100644 index 0000000..3b5e8f3 --- /dev/null +++ b/tools/linguist/shared/translator.cpp @@ -0,0 +1,559 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include "simtexth.h" + +#include <stdio.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <private/qtranslator_p.h> + +QT_BEGIN_NAMESPACE + +Translator::Translator() : + m_codecName("ISO-8859-1"), + m_locationsType(AbsoluteLocations) +{ +} + +void Translator::registerFileFormat(const FileFormat &format) +{ + //qDebug() << "Translator: Registering format " << format.extension; + QList<Translator::FileFormat> &formats = registeredFileFormats(); + for (int i = 0; i < formats.size(); ++i) + if (format.fileType == formats[i].fileType && format.priority < formats[i].priority) { + formats.insert(i, format); + return; + } + formats.append(format); +} + +QList<Translator::FileFormat> &Translator::registeredFileFormats() +{ + static QList<Translator::FileFormat> theFormats; + return theFormats; +} + +void Translator::replace(const TranslatorMessage &msg) +{ + int index = m_messages.indexOf(msg); + if (index == -1) + m_messages.append(msg); + else + m_messages[index] = msg; +} + +void Translator::replaceSorted(const TranslatorMessage &msg) +{ + int index = m_messages.indexOf(msg); + if (index == -1) + appendSorted(msg); + else + m_messages[index] = msg; +} + +void Translator::extend(const TranslatorMessage &msg) +{ + int index = m_messages.indexOf(msg); + if (index == -1) { + m_messages.append(msg); + } else { + m_messages[index].addReferenceUniq(msg.fileName(), msg.lineNumber()); + if (!msg.extraComment().isEmpty()) { + QString cmt = m_messages[index].extraComment(); + if (!cmt.isEmpty()) + cmt.append(QLatin1String("\n----------\n")); + cmt.append(msg.extraComment()); + m_messages[index].setExtraComment(cmt); + } + } +} + +void Translator::append(const TranslatorMessage &msg) +{ + m_messages.append(msg); +} + +void Translator::appendSorted(const TranslatorMessage &msg) +{ + int msgLine = msg.lineNumber(); + if (msgLine < 0) { + m_messages.append(msg); + return; + } + + int bestIdx = 0; // Best insertion point found so far + int bestScore = 0; // Its category: 0 = no hit, 1 = pre or post, 2 = middle + int bestSize = 0; // The length of the region. Longer is better within one category. + + // The insertion point to use should this region turn out to be the best one so far + int thisIdx = 0; + int thisScore = 0; + int thisSize = 0; + // Working vars + int prevLine = 0; + int curIdx = 0; + foreach (const TranslatorMessage &mit, m_messages) { + bool sameFile = mit.fileName() == msg.fileName(); + int curLine; + if (sameFile && (curLine = mit.lineNumber()) >= prevLine) { + if (msgLine >= prevLine && msgLine < curLine) { + thisIdx = curIdx; + thisScore = thisSize ? 2 : 1; + } + ++thisSize; + prevLine = curLine; + } else { + if (thisSize) { + if (!thisScore) { + thisIdx = curIdx; + thisScore = 1; + } + if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) { + bestIdx = thisIdx; + bestScore = thisScore; + bestSize = thisSize; + } + thisScore = 0; + thisSize = sameFile ? 1 : 0; + prevLine = 0; + } + } + ++curIdx; + } + if (thisSize && !thisScore) { + thisIdx = curIdx; + thisScore = 1; + } + if (thisScore > bestScore || (thisScore == bestScore && thisSize > bestSize)) + m_messages.insert(thisIdx, msg); + else if (bestScore) + m_messages.insert(bestIdx, msg); + else + m_messages.append(msg); +} + +static QString guessFormat(const QString &filename, const QString &format) +{ + if (format != QLatin1String("auto")) + return format; + + foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) { + if (filename.endsWith(QLatin1Char('.') + fmt.extension, Qt::CaseInsensitive)) + return fmt.extension; + } + + // the default format. + // FIXME: change to something more widely distributed later. + return QLatin1String("ts"); +} + +bool Translator::load(const QString &filename, ConversionData &cd, const QString &format) +{ + cd.m_sourceDir = QFileInfo(filename).absoluteDir(); + cd.m_sourceFileName = filename; + + QFile file; + if (filename.isEmpty() || filename == QLatin1String("-")) { + if (!file.open(stdin, QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open stdin!? (%1)") + .arg(file.errorString())); + return false; + } + } else { + file.setFileName(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + return false; + } + } + + QString fmt = guessFormat(filename, format); + + foreach (const FileFormat &format, registeredFileFormats()) { + if (fmt == format.extension) { + if (format.loader) + return (*format.loader)(*this, file, cd); + cd.appendError(QString(QLatin1String("No loader for format %1 found")) + .arg(fmt)); + return false; + } + } + + cd.appendError(QString(QLatin1String("Unknown format %1 for file %2")) + .arg(format, filename)); + return false; +} + + +bool Translator::save(const QString &filename, ConversionData &cd, const QString &format) const +{ + QFile file; + if (filename.isEmpty() || filename == QLatin1String("-")) { + if (!file.open(stdout, QIODevice::WriteOnly)) { + cd.appendError(QString::fromLatin1("Cannot open stdout!? (%1)") + .arg(file.errorString())); + return false; + } + } else { + file.setFileName(filename); + if (!file.open(QIODevice::WriteOnly)) { + cd.appendError(QString::fromLatin1("Cannot create %1: %2") + .arg(filename, file.errorString())); + return false; + } + } + + QString fmt = guessFormat(filename, format); + cd.m_targetDir = QFileInfo(filename).absoluteDir(); + + foreach (const FileFormat &format, registeredFileFormats()) { + if (fmt == format.extension) { + if (format.saver) + return (*format.saver)(*this, file, cd); + cd.appendError(QString(QLatin1String("Cannot save %1 files")).arg(fmt)); + return false; + } + } + + cd.appendError(QString(QLatin1String("Unknown format %1 for file %2")) + .arg(format).arg(filename)); + return false; +} + +QString Translator::makeLanguageCode(QLocale::Language language, QLocale::Country country) +{ + QLocale locale(language, country); + if (country == QLocale::AnyCountry) { + QString languageCode = locale.name().section(QLatin1Char('_'), 0, 0); + if (languageCode.length() <= 3) + return languageCode; + return QString(); + } else { + return locale.name(); + } +} + +void Translator::languageAndCountry(const QString &languageCode, + QLocale::Language *lang, QLocale::Country *country) +{ + QLocale locale(languageCode); + if (lang) + *lang = locale.language(); + + if (country) { + if (languageCode.indexOf(QLatin1Char('_')) != -1) + *country = locale.country(); + else + *country = QLocale::AnyCountry; + } +} + +bool Translator::release(QFile *iod, ConversionData &cd) const +{ + foreach (const FileFormat &format, registeredFileFormats()) { + if (format.extension == QLatin1String("qm")) + return (*format.saver)(*this, *iod, cd); + } + cd.appendError(QLatin1String("No .qm saver available.")); + return false; +} + +bool Translator::contains(const QString &context, + const QString &sourceText, const QString &comment) const +{ + return m_messages.contains(TranslatorMessage(context, sourceText, comment, + QString(), QString(), 0)); +} + +TranslatorMessage Translator::find(const QString &context, + const QString &sourceText, const QString &comment) const +{ + TranslatorMessage needle(context, sourceText, comment, QString(), QString(), 0); + int index = m_messages.indexOf(needle); + return index == -1 ? TranslatorMessage() : m_messages.at(index); +} + +TranslatorMessage Translator::find(const QString &context, + const QString &comment, const TranslatorMessage::References &refs) const +{ + if (!refs.isEmpty()) { + for (TMM::ConstIterator it = m_messages.constBegin(); it != m_messages.constEnd(); ++it) { + if (it->context() == context && it->comment() == comment) + foreach (const TranslatorMessage::Reference &itref, it->allReferences()) + foreach (const TranslatorMessage::Reference &ref, refs) + if (itref == ref) + return *it; + } + } + return TranslatorMessage(); +} + +bool Translator::contains(const QString &context) const +{ + foreach (const TranslatorMessage &msg, m_messages) + if (msg.context() == context && msg.sourceText().isEmpty()) + return true; + return false; +} + +TranslatorMessage Translator::find(const QString &context) const +{ + foreach (const TranslatorMessage &msg, m_messages) + if (msg.context() == context && msg.sourceText().isEmpty()) + return msg; + return TranslatorMessage(); +} + +void Translator::stripObsoleteMessages() +{ + TMM newmm; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) + if (it->type() != TranslatorMessage::Obsolete) + newmm.append(*it); + m_messages = newmm; +} + +void Translator::stripFinishedMessages() +{ + TMM newmm; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) + if (it->type() != TranslatorMessage::Finished) + newmm.append(*it); + m_messages = newmm; +} + +void Translator::stripEmptyContexts() +{ + TMM newmm; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) + if (it->sourceText() != QLatin1String(ContextComment)) + newmm.append(*it); + m_messages = newmm; +} + +void Translator::stripNonPluralForms() +{ + TMM newmm; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) + if (it->isPlural()) + newmm.append(*it); + m_messages = newmm; +} + +void Translator::stripIdenticalSourceTranslations() +{ + TMM newmm; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) { + // we need to have just one translation, and it be equal to the source + if (it->translations().count() != 1) + newmm.append(*it); + else if (it->translation() != it->sourceText()) + newmm.append(*it); + } + m_messages = newmm; +} + +void Translator::dropTranslations() +{ + for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ++it) { + if (it->type() == TranslatorMessage::Finished) + it->setType(TranslatorMessage::Unfinished); + it->setTranslation(QString()); + } +} + +QList<TranslatorMessage> Translator::findDuplicates() const +{ + QHash<TranslatorMessage, int> dups; + foreach (const TranslatorMessage &msg, m_messages) + dups[msg]++; + QList<TranslatorMessage> ret; + QHash<TranslatorMessage, int>::ConstIterator it = dups.constBegin(), end = dups.constEnd(); + for (; it != end; ++it) + if (it.value() > 1) + ret.append(it.key()); + return ret; +} + +// Used by lupdate to be able to search using absolute paths during merging +void Translator::makeFileNamesAbsolute(const QDir &originalPath) +{ + TMM newmm; + for (TMM::iterator it = m_messages.begin(); it != m_messages.end(); ++it) { + TranslatorMessage msg = *it; + msg.setReferences(TranslatorMessage::References()); + foreach (const TranslatorMessage::Reference &ref, it->allReferences()) { + QString fileName = ref.fileName(); + QFileInfo fi (fileName); + if (fi.isRelative()) + fileName = originalPath.absoluteFilePath(fileName); + msg.addReference(fileName, ref.lineNumber()); + } + newmm.append(msg); + } + m_messages = newmm; +} + +QList<TranslatorMessage> Translator::messages() const +{ + return m_messages; +} + +QList<TranslatorMessage> Translator::translatedMessages() const +{ + TMM result; + for (TMM::ConstIterator it = m_messages.begin(); it != m_messages.end(); ++it) + if (it->type() == TranslatorMessage::Finished) + result.append(*it); + return result; +} + +QStringList Translator::normalizedTranslations(const TranslatorMessage &msg, + QLocale::Language language, QLocale::Country country) +{ + QStringList translations = msg.translations(); + int numTranslations = 1; + if (msg.isPlural() && language != QLocale::C) { + QStringList forms; + if (getNumerusInfo(language, country, 0, &forms)) + numTranslations = forms.count(); // includes singular + } + + // make sure that the stringlist always have the size of the + // language's current numerus, or 1 if its not plural + if (translations.count() > numTranslations) { + for (int i = translations.count(); i > numTranslations; --i) + translations.removeLast(); + } else if (translations.count() < numTranslations) { + for (int i = translations.count(); i < numTranslations; ++i) + translations.append(QString()); + } + return translations; +} + +QStringList Translator::normalizedTranslations(const TranslatorMessage &msg, + ConversionData &cd, bool *ok) const +{ + QLocale::Language l; + QLocale::Country c; + languageAndCountry(languageCode(), &l, &c); + QStringList translns = normalizedTranslations(msg, l, c); + if (msg.translations().size() > translns.size() && ok) { + cd.appendError(QLatin1String( + "Removed plural forms as the target language has less " + "forms.\nIf this sounds wrong, possibly the target language is " + "not set or recognized.\n")); + *ok = false; + } + return translns; +} + +QString Translator::guessLanguageCodeFromFileName(const QString &filename) +{ + QString str = filename; + foreach (const FileFormat &format, registeredFileFormats()) { + if (str.endsWith(format.extension)) { + str = str.left(str.size() - format.extension.size() - 1); + break; + } + } + static QRegExp re(QLatin1String("[\\._]")); + while (true) { + QLocale locale(str); + //qDebug() << "LANGUAGE FROM " << str << "LANG: " << locale.language(); + if (locale.language() != QLocale::C) { + //qDebug() << "FOUND " << locale.name(); + return locale.name(); + } + int pos = str.indexOf(re); + if (pos == -1) + break; + str = str.mid(pos + 1); + } + //qDebug() << "LANGUAGE GUESSING UNSUCCESSFUL"; + return QString(); +} + +bool Translator::hasExtra(const QString &key) const +{ + return m_extra.contains(key); +} + +QString Translator::extra(const QString &key) const +{ + return m_extra[key]; +} + +void Translator::setExtra(const QString &key, const QString &value) +{ + m_extra[key] = value; +} + +void Translator::setCodecName(const QByteArray &name) +{ + QTextCodec *codec = QTextCodec::codecForName(name); + if (!codec) { + if (!name.isEmpty()) + qWarning("No QTextCodec for %s available. Using Latin1\n", name.constData()); + m_codecName.clear(); + } else { + m_codecName = codec->name(); + } +} + +void Translator::dump() const +{ + for (int i = 0; i != messageCount(); ++i) + message(i).dump(); +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/translator.h b/tools/linguist/shared/translator.h new file mode 100644 index 0000000..f4279da --- /dev/null +++ b/tools/linguist/shared/translator.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef METATRANSLATOR_H +#define METATRANSLATOR_H + +#include "translatormessage.h" + +#include <QDir> +#include <QList> +#include <QLocale> +#include <QString> + + +QT_BEGIN_NAMESPACE + +Q_DECLARE_TYPEINFO(TranslatorMessage, Q_MOVABLE_TYPE); + +class QIODevice; + +// A struct of "interesting" data passed to and from the load and save routines +class ConversionData +{ +public: + ConversionData() : + m_verbose(false), + m_ignoreUnfinished(false), + m_sortContexts(false), + m_noUiLines(false), + m_saveMode(SaveEverything) + {} + + // tag manipulation + const QStringList &dropTags() const { return m_dropTags; } + QStringList &dropTags() { return m_dropTags; } + const QDir &targetDir() const { return m_targetDir; } + bool isVerbose() const { return m_verbose; } + bool ignoreUnfinished() const { return m_ignoreUnfinished; } + bool sortContexts() const { return m_sortContexts; } + + void appendError(const QString &error) { m_errors.append(error); } + QString error() const { return m_errors.join(QLatin1String("\n")); } + QStringList errors() const { return m_errors; } + void clearErrors() { m_errors.clear(); } + +public: + QString m_defaultContext; + QByteArray m_codecForSource; // CPP specific + QString m_sourceFileName; + QString m_targetFileName; + QDir m_sourceDir; + QDir m_targetDir; // FIXME: TS spefic + QStringList m_dropTags; // tags to be dropped + QStringList m_errors; + bool m_verbose; + bool m_ignoreUnfinished; + bool m_sortContexts; + bool m_noUiLines; + TranslatorSaveMode m_saveMode; +}; + +class Translator +{ +public: + Translator(); + + bool load(const QString &filename, ConversionData &err, const QString &format /*= "auto"*/); + bool save(const QString &filename, ConversionData &err, const QString &format /*= "auto"*/) const; + bool release(QFile *iod, ConversionData &cd) const; + + bool contains(const QString &context, const QString &sourceText, + const QString &comment) const; + TranslatorMessage find(const QString &context, + const QString &sourceText, const QString &comment) const; + + TranslatorMessage find(const QString &context, + const QString &comment, const TranslatorMessage::References &refs) const; + + bool contains(const QString &context) const; + TranslatorMessage find(const QString &context) const; + + void replace(const TranslatorMessage &msg); + void replaceSorted(const TranslatorMessage &msg); + void extend(const TranslatorMessage &msg); // Only for single-location messages + void append(const TranslatorMessage &msg); + void appendSorted(const TranslatorMessage &msg); + + void stripObsoleteMessages(); + void stripFinishedMessages(); + void stripEmptyContexts(); + void stripNonPluralForms(); + void stripIdenticalSourceTranslations(); + void dropTranslations(); + QList<TranslatorMessage> findDuplicates() const; + void makeFileNamesAbsolute(const QDir &originalPath); + + void setCodecName(const QByteArray &name); + QByteArray codecName() const { return m_codecName; } + + QString languageCode() const { return m_language; } + QString sourceLanguageCode() const { return m_sourceLanguage; } + + enum LocationsType { NoLocations, RelativeLocations, AbsoluteLocations }; + void setLocationsType(LocationsType lt) { m_locationsType = lt; } + LocationsType locationsType() const { return m_locationsType; } + + static QString makeLanguageCode(QLocale::Language language, QLocale::Country country); + static void languageAndCountry(const QString &languageCode, + QLocale::Language *lang, QLocale::Country *country); + void setLanguageCode(const QString &languageCode) { m_language = languageCode; } + void setSourceLanguageCode(const QString &languageCode) { m_sourceLanguage = languageCode; } + static QString guessLanguageCodeFromFileName(const QString &fileName); + QList<TranslatorMessage> messages() const; + QList<TranslatorMessage> translatedMessages() const; + static QStringList normalizedTranslations(const TranslatorMessage &m, + QLocale::Language lang, QLocale::Country country); + QStringList normalizedTranslations(const TranslatorMessage &m, ConversionData &cd, bool *ok) const; + + int messageCount() const { return m_messages.size(); } + TranslatorMessage &message(int i) { return m_messages[i]; } + const TranslatorMessage &message(int i) const { return m_messages.at(i); } + void dump() const; + + // additional file format specific data + // note: use '<fileformat>:' as prefix for file format specific members, + // e.g. "po-flags", "po-msgid_plural" + typedef TranslatorMessage::ExtraData ExtraData; + QString extra(const QString &ba) const; + void setExtra(const QString &ba, const QString &var); + bool hasExtra(const QString &ba) const; + const ExtraData &extras() const { return m_extra; } + void setExtras(const ExtraData &extras) { m_extra = extras; } + + // registration of file formats + typedef bool (*SaveFunction)(const Translator &, QIODevice &out, ConversionData &data); + typedef bool (*LoadFunction)(Translator &, QIODevice &in, ConversionData &data); + struct FileFormat { + FileFormat() : loader(0), saver(0), priority(-1) {} + QString extension; // such as "ts", "xlf", ... + QString description; // human-readable description + LoadFunction loader; + SaveFunction saver; + enum FileType { SourceCode, TranslationSource, TranslationBinary } fileType; + int priority; // 0 = highest, -1 = invisible + }; + static void registerFileFormat(const FileFormat &format); + static QList<FileFormat> ®isteredFileFormats(); + + enum VariantSeparators { + DefaultVariantSeparator = 0x2762, // some weird character nobody ever heard of :-D + InternalVariantSeparator = 0x9c // unicode "STRING TERMINATOR" + }; + +private: + typedef QList<TranslatorMessage> TMM; // int stores the sequence position. + + TMM m_messages; + QByteArray m_codecName; + LocationsType m_locationsType; + + // A string beginning with a 2 or 3 letter language code (ISO 639-1 + // or ISO-639-2), followed by the optional country variant to distinguish + // between country-specific variations of the language. The language code + // and country code are always separated by '_' + // Note that the language part can also be a 3-letter ISO 639-2 code. + // Legal examples: + // 'pt' portuguese, assumes portuguese from portugal + // 'pt_BR' Brazilian portuguese (ISO 639-1 language code) + // 'por_BR' Brazilian portuguese (ISO 639-2 language code) + QString m_language; + QString m_sourceLanguage; + ExtraData m_extra; +}; + +bool getNumerusInfo(QLocale::Language language, QLocale::Country country, + QByteArray *rules, QStringList *forms); + +/* + This is a quick hack. The proper way to handle this would be + to extend Translator's interface. +*/ +#define ContextComment "QT_LINGUIST_INTERNAL_CONTEXT_COMMENT" + +QT_END_NAMESPACE + +#endif diff --git a/tools/linguist/shared/translatormessage.cpp b/tools/linguist/shared/translatormessage.cpp new file mode 100644 index 0000000..ab4301f --- /dev/null +++ b/tools/linguist/shared/translatormessage.cpp @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translatormessage.h" + +#include <qplatformdefs.h> + +#ifndef QT_NO_TRANSLATION + +#include <QDataStream> +#include <QDebug> + +#include <stdlib.h> + + +QT_BEGIN_NAMESPACE + +TranslatorMessage::TranslatorMessage() + : m_lineNumber(-1), m_type(Unfinished), m_utf8(false), m_plural(false) +{ +} + +TranslatorMessage::TranslatorMessage(const QString &context, + const QString &sourceText, const QString &comment, + const QString &userData, + const QString &fileName, int lineNumber, const QStringList &translations, + Type type, bool plural) + : m_context(context), m_sourcetext(sourceText), m_comment(comment), + m_userData(userData), + m_translations(translations), m_fileName(fileName), m_lineNumber(lineNumber), + m_type(type), m_utf8(false), m_plural(plural) +{ +} + +void TranslatorMessage::addReference(const QString &fileName, int lineNumber) +{ + if (m_fileName.isEmpty()) { + m_fileName = fileName; + m_lineNumber = lineNumber; + } else { + m_extraRefs.append(Reference(fileName, lineNumber)); + } +} + +void TranslatorMessage::addReferenceUniq(const QString &fileName, int lineNumber) +{ + if (m_fileName.isEmpty()) { + m_fileName = fileName; + m_lineNumber = lineNumber; + } else { + if (fileName == m_fileName && lineNumber == m_lineNumber) + return; + if (!m_extraRefs.isEmpty()) // Rather common case, so special-case it + foreach (const Reference &ref, m_extraRefs) + if (fileName == ref.fileName() && lineNumber == ref.lineNumber()) + return; + m_extraRefs.append(Reference(fileName, lineNumber)); + } +} + +void TranslatorMessage::clearReferences() +{ + m_fileName.clear(); + m_lineNumber = -1; + m_extraRefs.clear(); +} + +void TranslatorMessage::setReferences(const TranslatorMessage::References &refs0) +{ + if (!refs0.isEmpty()) { + References refs = refs0; + const Reference &ref = refs.takeFirst(); + m_fileName = ref.fileName(); + m_lineNumber = ref.lineNumber(); + m_extraRefs = refs; + } else { + clearReferences(); + } +} + +TranslatorMessage::References TranslatorMessage::allReferences() const +{ + References refs; + if (!m_fileName.isEmpty()) { + refs.append(Reference(m_fileName, m_lineNumber)); + refs += m_extraRefs; + } + return refs; +} + +static bool needs8BitHelper(const QString &ba) +{ + for (int i = ba.size(); --i >= 0; ) + if (ba.at(i).unicode() >= 0x80) + return true; + return false; +} + +bool TranslatorMessage::needs8Bit() const +{ + //dump(); + return needs8BitHelper(m_sourcetext) + || needs8BitHelper(m_comment) + || needs8BitHelper(m_context); +} + + +bool TranslatorMessage::operator==(const TranslatorMessage& m) const +{ + static QString msgIdPlural = QLatin1String("po-msgid_plural"); + + // Special treatment for context comments (empty source). + return (m_context == m.m_context) + && m_sourcetext == m.m_sourcetext + && m_extra[msgIdPlural] == m.m_extra[msgIdPlural] + && (m_sourcetext.isEmpty() || m_comment == m.m_comment); +} + + +bool TranslatorMessage::operator<(const TranslatorMessage& m) const +{ + if (m_context != m.m_context) + return m_context < m.m_context; + if (m_sourcetext != m.m_sourcetext) + return m_sourcetext < m.m_sourcetext; + return m_comment < m.m_comment; +} + +int qHash(const TranslatorMessage &msg) +{ + return + qHash(msg.context()) ^ + qHash(msg.sourceText()) ^ + qHash(msg.extra(QLatin1String("po-msgid_plural"))) ^ + qHash(msg.comment()); +} + +bool TranslatorMessage::hasExtra(const QString &key) const +{ + return m_extra.contains(key); +} + +QString TranslatorMessage::extra(const QString &key) const +{ + return m_extra[key]; +} + +void TranslatorMessage::setExtra(const QString &key, const QString &value) +{ + m_extra[key] = value; +} + +void TranslatorMessage::unsetExtra(const QString &key) +{ + m_extra.remove(key); +} + +void TranslatorMessage::dump() const +{ + qDebug() + << "\nId : " << m_id + << "\nContext : " << m_context + << "\nSource : " << m_sourcetext + << "\nComment : " << m_comment + << "\nUserData : " << m_userData + << "\nExtraComment : " << m_extraComment + << "\nTranslatorComment : " << m_translatorComment + << "\nTranslations : " << m_translations + << "\nFileName : " << m_fileName + << "\nLineNumber : " << m_lineNumber + << "\nType : " << m_type + << "\nPlural : " << m_plural + << "\nExtra : " << m_extra; +} + + +QT_END_NAMESPACE + +#endif // QT_NO_TRANSLATION diff --git a/tools/linguist/shared/translatormessage.h b/tools/linguist/shared/translatormessage.h new file mode 100644 index 0000000..fa37ff5 --- /dev/null +++ b/tools/linguist/shared/translatormessage.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TRANSLATORMESSAGE_H +#define TRANSLATORMESSAGE_H + +#ifndef QT_NO_TRANSLATION + +#include <QString> +#include <QStringList> +#include <QHash> + + +QT_BEGIN_NAMESPACE + +enum TranslatorSaveMode { SaveEverything, SaveStripped }; + +class TranslatorMessage +{ +public: + enum Type { Unfinished, Finished, Obsolete }; + typedef QHash<QString, QString> ExtraData; + class Reference + { + QString m_fileName; + int m_lineNumber; + public: + Reference(const QString &n, int l) : m_fileName(n), m_lineNumber(l) {} + bool operator==(const Reference &other) const + { return fileName() == other.fileName() && lineNumber() == other.lineNumber(); } + QString fileName() const { return m_fileName; } + int lineNumber() const { return m_lineNumber; } + }; + typedef QList<Reference> References; + + TranslatorMessage(); + TranslatorMessage(const QString &context, const QString &sourceText, + const QString &comment, const QString &userData, + const QString &fileName, int lineNumber, + const QStringList &translations = QStringList(), + Type type = Unfinished, bool plural = false); + + uint hash() const; + + QString id() const { return m_id; } + void setId(const QString &id) { m_id = id; } + + QString context() const { return m_context; } + void setContext(const QString &context) { m_context = context; } + + QString sourceText() const { return m_sourcetext; } + void setSourceText(const QString &sourcetext) { m_sourcetext = sourcetext; } + QString oldSourceText() const { return m_oldsourcetext; } + void setOldSourceText(const QString &oldsourcetext) { m_oldsourcetext = oldsourcetext; } + + QString comment() const { return m_comment; } + void setComment(const QString &comment) { m_comment = comment; } + QString oldComment() const { return m_oldcomment; } + void setOldComment(const QString &oldcomment) { m_oldcomment = oldcomment; } + + QStringList translations() const { return m_translations; } + void setTranslations(const QStringList &translations) { m_translations = translations; } + QString translation() const { return m_translations.value(0); } + void setTranslation(const QString &translation) { m_translations = QStringList(translation); } + void appendTranslation(const QString &translation) { m_translations.append(translation); } + bool isTranslated() const + { + foreach (const QString &trans, m_translations) + if (!trans.isEmpty()) + return true; + return false; + } + + bool operator==(const TranslatorMessage& m) const; + bool operator<(const TranslatorMessage& m) const; + + QString fileName() const { return m_fileName; } + void setFileName(const QString &fileName) { m_fileName = fileName; } + int lineNumber() const { return m_lineNumber; } + void setLineNumber(int lineNumber) { m_lineNumber = lineNumber; } + void clearReferences(); + void setReferences(const References &refs); + void addReference(const QString &fileName, int lineNumber); + void addReference(const Reference &ref) { addReference(ref.fileName(), ref.lineNumber()); } + void addReferenceUniq(const QString &fileName, int lineNumber); + References extraReferences() const { return m_extraRefs; } + References allReferences() const; + QString userData() const { return m_userData; } + void setUserData(const QString &userData) { m_userData = userData; } + QString extraComment() const { return m_extraComment; } + void setExtraComment(const QString &extraComment) { m_extraComment = extraComment; } + QString translatorComment() const { return m_translatorComment; } + void setTranslatorComment(const QString &translatorComment) { m_translatorComment = translatorComment; } + + bool isNull() const { return m_sourcetext.isNull() && m_lineNumber == -1 && m_translations.isEmpty(); } + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + bool isUtf8() const { return m_utf8; } // codecForTr override + void setUtf8(bool on) { m_utf8 = on; } + bool isPlural() const { return m_plural; } + void setPlural(bool isplural) { m_plural = isplural; } + + // note: use '<fileformat>:' as prefix for file format specific members, + // e.g. "po-msgid_plural" + QString extra(const QString &ba) const; + void setExtra(const QString &ba, const QString &var); + bool hasExtra(const QString &ba) const; + const ExtraData &extras() const { return m_extra; } + void setExtras(const ExtraData &extras) { m_extra = extras; } + void unsetExtra(const QString &key); + + bool needs8Bit() const; + void dump() const; + +private: + QString m_id; + QString m_context; + QString m_sourcetext; + QString m_oldsourcetext; + QString m_comment; + QString m_oldcomment; + QString m_userData; + ExtraData m_extra; // PO flags, PO plurals + QString m_extraComment; + QString m_translatorComment; + QStringList m_translations; + QString m_fileName; + int m_lineNumber; + References m_extraRefs; + + Type m_type; + bool m_utf8; + bool m_plural; +}; + +int qHash(const TranslatorMessage &msg); + +QT_END_NAMESPACE + +#endif // QT_NO_TRANSLATION + +#endif // TRANSLATORMESSAGE_H diff --git a/tools/linguist/shared/translatortools.cpp b/tools/linguist/shared/translatortools.cpp new file mode 100644 index 0000000..dcff546 --- /dev/null +++ b/tools/linguist/shared/translatortools.cpp @@ -0,0 +1,505 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translatortools.h" + +#include "simtexth.h" +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QMap> +#include <QtCore/QStringList> +#include <QtCore/QTextCodec> +#include <QtCore/QVector> + +typedef QList<TranslatorMessage> TML; +typedef QMap<QString, TranslatorMessage> TMM; + + +QT_BEGIN_NAMESPACE + +static bool isDigitFriendly(QChar c) +{ + return c.isPunct() || c.isSpace(); +} + +static int numberLength(const QString &s, int i) +{ + if (i < s.size() || !s.at(i).isDigit()) + return 0; + + int pos = i; + do { + ++i; + } while (i < s.size() + && (s.at(i).isDigit() + || (isDigitFriendly(s[i]) + && i + 1 < s.size() + && (s[i + 1].isDigit() + || (isDigitFriendly(s[i + 1]) + && i + 2 < s.size() + && s[i + 2].isDigit()))))); + return i - pos; +} + + +/* + Returns a version of 'key' where all numbers have been replaced by zeroes. If + there were none, returns "". +*/ +static QString zeroKey(const QString &key) +{ + QString zeroed; + bool metSomething = false; + + for (int i = 0; i != key.size(); ++i) { + int len = numberLength(key, i); + if (len > 0) { + i += len; + zeroed.append(QLatin1Char('0')); + metSomething = true; + } else { + zeroed.append(key.at(i)); + } + } + return metSomething ? zeroed : QString(); +} + +static QString translationAttempt(const QString &oldTranslation, + const QString &oldSource, const QString &newSource) +{ + int p = zeroKey(oldSource).count(QLatin1Char('0')); + QString attempt; + QStringList oldNumbers; + QStringList newNumbers; + QVector<bool> met(p); + QVector<int> matchedYet(p); + int i, j; + int k = 0, ell, best; + int m, n; + int pass; + + /* + This algorithm is hard to follow, so we'll consider an example + all along: oldTranslation is "XeT 3.0", oldSource is "TeX 3.0" + and newSource is "XeT 3.1". + + First, we set up two tables: oldNumbers and newNumbers. In our + example, oldNumber[0] is "3.0" and newNumber[0] is "3.1". + */ + for (i = 0, j = 0; i < oldSource.size(); i++, j++) { + m = numberLength(oldSource, i); + n = numberLength(newSource, j); + if (m > 0) { + oldNumbers.append(oldSource.mid(i, m + 1)); + newNumbers.append(newSource.mid(j, n + 1)); + i += m; + j += n; + met[k] = false; + matchedYet[k] = 0; + k++; + } + } + + /* + We now go over the old translation, "XeT 3.0", one letter at a + time, looking for numbers found in oldNumbers. Whenever such a + number is met, it is replaced with its newNumber equivalent. In + our example, the "3.0" of "XeT 3.0" becomes "3.1". + */ + for (i = 0; i < oldTranslation.length(); i++) { + attempt += oldTranslation[i]; + for (k = 0; k < p; k++) { + if (oldTranslation[i] == oldNumbers[k][matchedYet[k]]) + matchedYet[k]++; + else + matchedYet[k] = 0; + } + + /* + Let's find out if the last character ended a match. We make + two passes over the data. In the first pass, we try to + match only numbers that weren't matched yet; if that fails, + the second pass does the trick. This is useful in some + suspicious cases, flagged below. + */ + for (pass = 0; pass < 2; pass++) { + best = p; // an impossible value + for (k = 0; k < p; k++) { + if ((!met[k] || pass > 0) && + matchedYet[k] == oldNumbers[k].length() && + numberLength(oldTranslation, i + 1 - matchedYet[k]) == matchedYet[k]) { + // the longer the better + if (best == p || matchedYet[k] > matchedYet[best]) + best = k; + } + } + if (best != p) { + attempt.truncate(attempt.length() - matchedYet[best]); + attempt += newNumbers[best]; + met[best] = true; + for (k = 0; k < p; k++) + matchedYet[k] = 0; + break; + } + } + } + + /* + We flag two kinds of suspicious cases. They are identified as + such with comments such as "{2000?}" at the end. + + Example of the first kind: old source text "TeX 3.0" translated + as "XeT 2.0" is flagged "TeX 2.0 {3.0?}", no matter what the + new text is. + */ + for (k = 0; k < p; k++) { + if (!met[k]) + attempt += QString(QLatin1String(" {")) + newNumbers[k] + QString(QLatin1String("?}")); + } + + /* + Example of the second kind: "1 of 1" translated as "1 af 1", + with new source text "1 of 2", generates "1 af 2 {1 or 2?}" + because it's not clear which of "1 af 2" and "2 af 1" is right. + */ + for (k = 0; k < p; k++) { + for (ell = 0; ell < p; ell++) { + if (k != ell && oldNumbers[k] == oldNumbers[ell] && + newNumbers[k] < newNumbers[ell]) + attempt += QString(QLatin1String(" {")) + newNumbers[k] + QString(QLatin1String(" or ")) + + newNumbers[ell] + QString(QLatin1String("?}")); + } + } + return attempt; +} + + +/* + Augments a Translator with translations easily derived from + similar existing (probably obsolete) translations. + + For example, if "TeX 3.0" is translated as "XeT 3.0" and "TeX 3.1" + has no translation, "XeT 3.1" is added to the translator and is + marked Unfinished. + + Returns the number of additional messages that this heuristic translated. +*/ +int applyNumberHeuristic(Translator &tor) +{ + TMM translated, untranslated; + TMM::Iterator t, u; + TML all = tor.messages(); + TML::Iterator it; + int inserted = 0; + + for (it = all.begin(); it != all.end(); ++it) { + bool hasTranslation = it->isTranslated(); + if (it->type() == TranslatorMessage::Unfinished) { + if (!hasTranslation) + untranslated.insert(it->context() + QLatin1Char('\n') + + it->sourceText() + QLatin1Char('\n') + + it->comment(), *it); + } else if (hasTranslation && it->translations().count() == 1) { + translated.insert(zeroKey(it->sourceText()), *it); + } + } + + for (u = untranslated.begin(); u != untranslated.end(); ++u) { + t = translated.find(zeroKey((*u).sourceText())); + if (t != translated.end() && !t.key().isEmpty() + && t->sourceText() != u->sourceText()) { + TranslatorMessage m = *u; + m.setTranslation(translationAttempt(t->translation(), t->sourceText(), + u->sourceText())); + tor.replace(m); + inserted++; + } + } + return inserted; +} + + +/* + Augments a Translator with trivially derived translations. + + For example, if "Enabled:" is consistendly translated as "Eingeschaltet:" no + matter the context or the comment, "Eingeschaltet:" is added as the + translation of any untranslated "Enabled:" text and is marked Unfinished. + + Returns the number of additional messages that this heuristic translated. +*/ + +int applySameTextHeuristic(Translator &tor) +{ + TMM translated; + TMM avoid; + TMM::Iterator t; + TML untranslated; + TML::Iterator u; + TML all = tor.messages(); + TML::Iterator it; + int inserted = 0; + + for (it = all.begin(); it != all.end(); ++it) { + if (!it->isTranslated()) { + if (it->type() == TranslatorMessage::Unfinished) + untranslated.append(*it); + } else { + QString key = it->sourceText(); + t = translated.find(key); + if (t != translated.end()) { + /* + The same source text is translated at least two + different ways. Do nothing then. + */ + if (t->translations() != it->translations()) { + translated.remove(key); + avoid.insert(key, *it); + } + } else if (!avoid.contains(key)) { + translated.insert(key, *it); + } + } + } + + for (u = untranslated.begin(); u != untranslated.end(); ++u) { + QString key = u->sourceText(); + t = translated.find(key); + if (t != translated.end()) { + TranslatorMessage m = *u; + m.setTranslations(t->translations()); + tor.replace(m); + ++inserted; + } + } + return inserted; +} + + + +/* + Merges two Translator objects. The first one + is a set of source texts and translations for a previous version of + the internationalized program; the second one is a set of fresh + source texts newly extracted from the source code, without any + translation yet. +*/ + +Translator merge(const Translator &tor, const Translator &virginTor, + UpdateOptions options, QString &err) +{ + int known = 0; + int neww = 0; + int obsoleted = 0; + int similarTextHeuristicCount = 0; + + Translator outTor; + outTor.setLanguageCode(tor.languageCode()); + outTor.setSourceLanguageCode(tor.sourceLanguageCode()); + outTor.setLocationsType(tor.locationsType()); + outTor.setCodecName(tor.codecName()); + + /* + The types of all the messages from the vernacular translator + are updated according to the virgin translator. + */ + foreach (TranslatorMessage m, tor.messages()) { + TranslatorMessage::Type newType = TranslatorMessage::Finished; + + if (m.sourceText().isEmpty()) { + // context/file comment + TranslatorMessage mv = virginTor.find(m.context()); + if (!mv.isNull()) + m.setComment(mv.comment()); + } else { + TranslatorMessage mv = virginTor.find(m.context(), m.sourceText(), m.comment()); + if (mv.isNull()) { + if (!(options & HeuristicSimilarText)) { + newType = TranslatorMessage::Obsolete; + if (m.type() != TranslatorMessage::Obsolete) + obsoleted++; + m.clearReferences(); + } else { + mv = virginTor.find(m.context(), m.comment(), m.allReferences()); + if (mv.isNull()) { + // did not find it in the virgin, mark it as obsolete + newType = TranslatorMessage::Obsolete; + if (m.type() != TranslatorMessage::Obsolete) + obsoleted++; + m.clearReferences(); + } else { + // Do not just accept it if its on the same line number, + // but different source text. + // Also check if the texts are more or less similar before + // we consider them to represent the same message... + if (getSimilarityScore(m.sourceText(), mv.sourceText()) >= textSimilarityThreshold) { + // It is just slightly modified, assume that it is the same string + + // Mark it as unfinished. (Since the source text + // was changed it might require re-translating...) + newType = TranslatorMessage::Unfinished; + ++similarTextHeuristicCount; + neww++; + + m.setOldSourceText(m.sourceText()); + m.setSourceText(mv.sourceText()); + const QString &oldpluralsource = m.extra(QLatin1String("po-msgid_plural")); + if (!oldpluralsource.isEmpty()) { + m.setExtra(QLatin1String("po-old_msgid_plural"), oldpluralsource); + m.unsetExtra(QLatin1String("po-msgid_plural")); + } + m.setReferences(mv.allReferences()); // Update secondary references + m.setPlural(mv.isPlural()); + m.setUtf8(mv.isUtf8()); + m.setExtraComment(mv.extraComment()); + } else { + // The virgin and vernacular sourceTexts are so + // different that we could not find it. + newType = TranslatorMessage::Obsolete; + if (m.type() != TranslatorMessage::Obsolete) + obsoleted++; + m.clearReferences(); + } + } + } + } else { + switch (m.type()) { + case TranslatorMessage::Finished: + default: + if (m.isPlural() == mv.isPlural()) { + newType = TranslatorMessage::Finished; + } else { + newType = TranslatorMessage::Unfinished; + } + known++; + break; + case TranslatorMessage::Unfinished: + newType = TranslatorMessage::Unfinished; + known++; + break; + case TranslatorMessage::Obsolete: + newType = TranslatorMessage::Unfinished; + neww++; + } + + // Always get the filename and linenumber info from the + // virgin Translator, in case it has changed location. + // This should also enable us to read a file that does not + // have the <location> element. + // why not use operator=()? Because it overwrites e.g. userData. + m.setReferences(mv.allReferences()); + m.setPlural(mv.isPlural()); + m.setUtf8(mv.isUtf8()); + m.setExtraComment(mv.extraComment()); + } + } + + m.setType(newType); + outTor.append(m); + } + + /* + Messages found only in the virgin translator are added to the + vernacular translator. + */ + foreach (const TranslatorMessage &mv, virginTor.messages()) { + if (mv.sourceText().isEmpty()) { + if (tor.contains(mv.context())) + continue; + } else { + if (tor.contains(mv.context(), mv.sourceText(), mv.comment())) + continue; + if (options & HeuristicSimilarText) { + TranslatorMessage m = tor.find(mv.context(), mv.comment(), mv.allReferences()); + if (!m.isNull()) { + if (getSimilarityScore(m.sourceText(), mv.sourceText()) >= textSimilarityThreshold) + continue; + } + } + } + if (options & NoLocations) + outTor.append(mv); + else + outTor.appendSorted(mv); + if (!mv.sourceText().isEmpty()) + ++neww; + } + + /* + The same-text heuristic handles cases where a message has an + obsolete counterpart with a different context or comment. + */ + int sameTextHeuristicCount = (options & HeuristicSameText) ? applySameTextHeuristic(outTor) : 0; + + /* + The number heuristic handles cases where a message has an + obsolete counterpart with mostly numbers differing in the + source text. + */ + int sameNumberHeuristicCount = (options & HeuristicNumber) ? applyNumberHeuristic(outTor) : 0; + + if (options & Verbose) { + int totalFound = neww + known; + err += QObject::tr(" Found %n source text(s) (%1 new and %2 already existing)\n", 0, totalFound).arg(neww).arg(known); + + if (obsoleted) { + if (options & NoObsolete) { + err += QObject::tr(" Removed %n obsolete entries\n", 0, obsoleted); + } else { + err += QObject::tr(" Kept %n obsolete entries\n", 0, obsoleted); + } + } + + if (sameNumberHeuristicCount) + err += QObject::tr(" Number heuristic provided %n translation(s)\n", + 0, sameNumberHeuristicCount); + if (sameTextHeuristicCount) + err += QObject::tr(" Same-text heuristic provided %n translation(s)\n", + 0, sameTextHeuristicCount); + if (similarTextHeuristicCount) + err += QObject::tr(" Similar-text heuristic provided %n translation(s)\n", + 0, similarTextHeuristicCount); + } + return outTor; +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/translatortools.h b/tools/linguist/shared/translatortools.h new file mode 100644 index 0000000..9eaf024 --- /dev/null +++ b/tools/linguist/shared/translatortools.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LUPDATE_H +#define LUPDATE_H + +#include "qglobal.h" + +#include <QList> + +QT_BEGIN_NAMESPACE + +class QString; +class Translator; +class TranslatorMessage; + +enum UpdateOption { + Verbose = 1, + NoObsolete = 2, + PluralOnly = 4, + NoSort = 8, + HeuristicSameText = 16, + HeuristicSimilarText = 32, + HeuristicNumber = 64, + AbsoluteLocations = 256, + RelativeLocations = 512, + NoLocations = 1024, + NoUiLines = 2048 +}; + +Q_DECLARE_FLAGS(UpdateOptions, UpdateOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateOptions) + +Translator merge(const Translator &tor, const Translator &virginTor, + UpdateOptions options, QString &err); + +QT_END_NAMESPACE + +#endif diff --git a/tools/linguist/shared/translatortools.pri b/tools/linguist/shared/translatortools.pri new file mode 100644 index 0000000..2b6de8c --- /dev/null +++ b/tools/linguist/shared/translatortools.pri @@ -0,0 +1,11 @@ + + +INCLUDEPATH *= $$PWD + +SOURCES += \ + $$PWD/translatortools.cpp \ + $$PWD/simtexth.cpp + +HEADERS += \ + $$PWD/translatortools.h + diff --git a/tools/linguist/shared/ts.cpp b/tools/linguist/shared/ts.cpp new file mode 100644 index 0000000..2e7d40f --- /dev/null +++ b/tools/linguist/shared/ts.cpp @@ -0,0 +1,755 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <QtXml/QXmlStreamReader> +#include <QtXml/QXmlStreamAttribute> + +#define STRINGIFY_INTERNAL(x) #x +#define STRINGIFY(x) STRINGIFY_INTERNAL(x) +#define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s))) + +QT_BEGIN_NAMESPACE + +/* + * The encodings are a total mess. + * A Translator has a codecForTr(). Each message's text will be passed to tr() + * in that encoding or as UTF-8 to trUtf8() if it is flagged as such. + * For ts 2.0, the file content is always uniformly in UTF-8. The file stores + * the codecForTr default and marks deviating messages accordingly. + * For ts 1.1, the file content is in mixed encoding. Each message is encoded + * the way it will be passed to tr() (with 8-bit characters encoded as numeric + * entities) or trUtf8(). The file stores the encoding and codecForTr in one + * attribute, for both the default and each deviating message. + */ + + +QDebug &operator<<(QDebug &d, const QXmlStreamAttribute &attr) +{ + return d << "[" << attr.name().toString() << "," << attr.value().toString() << "]"; +} + + +class TSReader : public QXmlStreamReader +{ +public: + TSReader(QIODevice &dev, ConversionData &cd) + : QXmlStreamReader(&dev), m_cd(cd) + {} + + // the "real thing" + bool read(Translator &translator); + +private: + bool elementStarts(const QString &str) const + { + return isStartElement() && name() == str; + } + + bool isWhiteSpace() const + { + return isCharacters() && text().toString().trimmed().isEmpty(); + } + + // needed to expand <byte ... /> + QString readContents(); + // needed to join <lengthvariant>s + QString readTransContents(); + + void handleError(); + + ConversionData &m_cd; +}; + +void TSReader::handleError() +{ + if (isComment()) + return; + if (hasError() && error() == CustomError) // raised by readContents + return; + + const QString loc = QString::fromLatin1("at %3:%1:%2") + .arg(lineNumber()).arg(columnNumber()).arg(m_cd.m_sourceFileName); + + switch (tokenType()) { + case NoToken: // Cannot happen + default: // likewise + case Invalid: + raiseError(QString::fromLatin1("Parse error %1: %2").arg(loc, errorString())); + break; + case StartElement: + raiseError(QString::fromLatin1("Unexpected tag <%1> %2").arg(name().toString(), loc)); + break; + case Characters: + { + QString tok = text().toString(); + if (tok.length() > 30) + tok = tok.left(30) + QLatin1String("[...]"); + raiseError(QString::fromLatin1("Unexpected characters '%1' %2").arg(tok, loc)); + } + break; + case EntityReference: + raiseError(QString::fromLatin1("Unexpected entity '&%1;' %2").arg(name().toString(), loc)); + break; + case ProcessingInstruction: + raiseError(QString::fromLatin1("Unexpected processing instruction %1").arg(loc)); + break; + } +} + +static QString byteValue(QString value) +{ + int base = 10; + if (value.startsWith(QLatin1String("x"))) { + base = 16; + value.remove(0, 1); + } + int n = value.toUInt(0, base); + return (n != 0) ? QString(QChar(n)) : QString(); +} + +QString TSReader::readContents() +{ + STRING(byte); + STRING(value); + + QString result; + while (!atEnd()) { + readNext(); + if (isEndElement()) { + break; + } else if (isCharacters()) { + result += text(); + } else if (elementStarts(strbyte)) { + // <byte value="..."> + result += byteValue(attributes().value(strvalue).toString()); + readNext(); + if (!isEndElement()) { + handleError(); + break; + } + } else { + handleError(); + break; + } + } + //qDebug() << "TEXT: " << result; + return result; +} + +QString TSReader::readTransContents() +{ + STRING(lengthvariant); + STRING(variants); + STRING(yes); + + if (attributes().value(strvariants) == stryes) { + QString result; + while (!atEnd()) { + readNext(); + if (isEndElement()) { + break; + } else if (isWhiteSpace()) { + // ignore these, just whitespace + } else if (elementStarts(strlengthvariant)) { + if (!result.isEmpty()) + result += QChar(Translator::DefaultVariantSeparator); + result += readContents(); + } else { + handleError(); + break; + } + } + return result; + } else { + return readContents(); + } +} + +bool TSReader::read(Translator &translator) +{ + STRING(byte); + STRING(comment); + STRING(context); + STRING(defaultcodec); + STRING(encoding); + STRING(extracomment); + STRING(filename); + STRING(id); + STRING(language); + STRING(line); + STRING(location); + STRING(message); + STRING(name); + STRING(numerus); + STRING(numerusform); + STRING(obsolete); + STRING(oldcomment); + STRING(oldsource); + STRING(source); + STRING(sourcelanguage); + STRING(translation); + STRING(translatorcomment); + STRING(true); + STRING(TS); + STRING(type); + STRING(unfinished); + STRING(userdata); + STRING(utf8); + STRING(value); + STRING(version); + STRING(yes); + + static const QString strextrans(QLatin1String("extra-")); + static const QString strUtf8(QLatin1String("UTF-8")); + + while (!atEnd()) { + readNext(); + if (isStartDocument()) { + // <!DOCTYPE TS> + //qDebug() << attributes(); + } else if (isEndDocument()) { + // <!DOCTYPE TS> + //qDebug() << attributes(); + } else if (isDTD()) { + // <!DOCTYPE TS> + //qDebug() << tokenString(); + } else if (elementStarts(strTS)) { + // <TS> + //qDebug() << "TS " << attributes(); + QHash<QString, int> currentLine; + QString currentFile; + + QXmlStreamAttributes atts = attributes(); + //QString version = atts.value(strversion).toString(); + translator.setLanguageCode(atts.value(strlanguage).toString()); + translator.setSourceLanguageCode(atts.value(strsourcelanguage).toString()); + while (!atEnd()) { + readNext(); + if (isEndElement()) { + // </TS> found, finish local loop + break; + } else if (isWhiteSpace()) { + // ignore these, just whitespace + } else if (elementStarts(strdefaultcodec)) { + // <defaultcodec> + translator.setCodecName(readElementText().toLatin1()); + // </defaultcodec> + } else if (isStartElement() + && name().toString().startsWith(strextrans)) { + // <extra-...> + QString tag = name().toString(); + translator.setExtra(tag.mid(6), readContents()); + // </extra-...> + } else if (elementStarts(strcontext)) { + // <context> + QString context; + while (!atEnd()) { + readNext(); + if (isEndElement()) { + // </context> found, finish local loop + break; + } else if (isWhiteSpace()) { + // ignore these, just whitespace + } else if (elementStarts(strname)) { + // <name> + context = readElementText(); + // </name> + } else if (elementStarts(strmessage)) { + // <message> + TranslatorMessage::References refs; + QString currentMsgFile = currentFile; + + TranslatorMessage msg; + msg.setId(attributes().value(strid).toString()); + msg.setContext(context); + msg.setType(TranslatorMessage::Finished); + msg.setPlural(attributes().value(strnumerus) == stryes); + msg.setUtf8(attributes().value(strutf8) == strtrue + || attributes().value(strencoding) == strUtf8); + while (!atEnd()) { + readNext(); + if (isEndElement()) { + // </message> found, finish local loop + msg.setReferences(refs); + translator.append(msg); + break; + } else if (isWhiteSpace()) { + // ignore these, just whitespace + } else if (elementStarts(strsource)) { + // <source>...</source> + msg.setSourceText(readContents()); + } else if (elementStarts(stroldsource)) { + // <oldsource>...</oldsource> + msg.setOldSourceText(readContents()); + } else if (elementStarts(stroldcomment)) { + // <oldcomment>...</oldcomment> + msg.setOldComment(readContents()); + } else if (elementStarts(strextracomment)) { + // <extracomment>...</extracomment> + msg.setExtraComment(readContents()); + } else if (elementStarts(strtranslatorcomment)) { + // <translatorcomment>...</translatorcomment> + msg.setTranslatorComment(readContents()); + } else if (elementStarts(strlocation)) { + // <location/> + QXmlStreamAttributes atts = attributes(); + QString fileName = atts.value(strfilename).toString(); + if (fileName.isEmpty()) { + fileName = currentMsgFile; + } else { + if (refs.isEmpty()) + currentFile = fileName; + currentMsgFile = fileName; + } + const QString lin = atts.value(strline).toString(); + if (lin.isEmpty()) { + translator.setLocationsType(Translator::RelativeLocations); + refs.append(TranslatorMessage::Reference(fileName, -1)); + } else { + bool bOK; + int lineNo = lin.toInt(&bOK); + if (bOK) { + if (lin.startsWith(QLatin1Char('+')) || lin.startsWith(QLatin1Char('-'))) { + lineNo = (currentLine[fileName] += lineNo); + translator.setLocationsType(Translator::RelativeLocations); + } else { + translator.setLocationsType(Translator::AbsoluteLocations); + } + refs.append(TranslatorMessage::Reference(fileName, lineNo)); + } + } + readContents(); + } else if (elementStarts(strcomment)) { + // <comment>...</comment> + msg.setComment(readContents()); + } else if (elementStarts(struserdata)) { + // <userdata>...</userdata> + msg.setUserData(readContents()); + } else if (elementStarts(strtranslation)) { + // <translation> + QXmlStreamAttributes atts = attributes(); + QStringRef type = atts.value(strtype); + if (type == strunfinished) + msg.setType(TranslatorMessage::Unfinished); + else if (type == strobsolete) + msg.setType(TranslatorMessage::Obsolete); + if (msg.isPlural()) { + QStringList translations; + while (!atEnd()) { + readNext(); + if (isEndElement()) { + break; + } else if (isWhiteSpace()) { + // ignore these, just whitespace + } else if (elementStarts(strnumerusform)) { + translations.append(readTransContents()); + } else { + handleError(); + break; + } + } + msg.setTranslations(translations); + } else { + msg.setTranslation(readTransContents()); + } + // </translation> + } else if (isStartElement() + && name().toString().startsWith(strextrans)) { + // <extra-...> + QString tag = name().toString(); + msg.setExtra(tag.mid(6), readContents()); + // </extra-...> + } else { + handleError(); + } + } + // </message> + } else { + handleError(); + } + } + // </context> + } else { + handleError(); + } + } // </TS> + } else { + handleError(); + } + } + if (hasError()) { + m_cd.appendError(errorString()); + return false; + } + return true; +} + +static QString numericEntity(int ch) +{ + return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>") + : QLatin1String("&#x%1;")) .arg(ch, 0, 16); +} + +static QString protect(const QString &str) +{ + QString result; + result.reserve(str.length() * 12 / 10); + for (int i = 0; i != str.size(); ++i) { + uint c = str.at(i).unicode(); + switch (c) { + case '\"': + result += QLatin1String("""); + break; + case '&': + result += QLatin1String("&"); + break; + case '>': + result += QLatin1String(">"); + break; + case '<': + result += QLatin1String("<"); + break; + case '\'': + result += QLatin1String("'"); + break; + default: + if (c < 0x20 && c != '\r' && c != '\n' && c != '\t') + result += numericEntity(c); + else // this also covers surrogates + result += QChar(c); + } + } + return result; +} + +static QString evilBytes(const QString& str, + bool isUtf8, int format, const QByteArray &codecName) +{ + //qDebug() << "EVIL: " << str << isUtf8 << format << codecName; + if (isUtf8) + return protect(str); + if (format == 20) + return protect(str); + if (codecName == "UTF-8") + return protect(str); + QTextCodec *codec = QTextCodec::codecForName(codecName); + if (!codec) + return protect(str); + QString t = QString::fromLatin1(codec->fromUnicode(protect(str)).data()); + int len = (int) t.length(); + QString result; + // FIXME: Factor is sensible only for latin scripts, probably. + result.reserve(t.length() * 2); + for (int k = 0; k < len; k++) { + if (t[k].unicode() >= 0x7f) + result += numericEntity(t[k].unicode()); + else + result += t[k]; + } + return result; +} + +static void writeExtras(QTextStream &t, const char *indent, + const TranslatorMessage::ExtraData &extras, const QRegExp &drops) +{ + for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) { + if (!drops.exactMatch(it.key())) { + t << indent << "<extra-" << it.key() << '>' + << protect(it.value()) + << "</extra-" << it.key() << ">\n"; + } + } +} + +static void writeVariants(QTextStream &t, const char *indent, const QString &input) +{ + int offset; + if ((offset = input.indexOf(QChar(Translator::DefaultVariantSeparator))) >= 0) { + t << " variants=\"yes\">"; + int start = 0; + forever { + t << "\n " << indent << "<lengthvariant>" + << protect(input.mid(start, offset - start)) + << "</lengthvariant>"; + if (offset == input.length()) + break; + start = offset + 1; + offset = input.indexOf(QChar(Translator::DefaultVariantSeparator), start); + if (offset < 0) + offset = input.length(); + } + t << "\n" << indent; + } else { + t << ">" << protect(input); + } +} + +bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd, int format) +{ + bool result = true; + QTextStream t(&dev); + t.setCodec(QTextCodec::codecForName("UTF-8")); + bool trIsUtf8 = (translator.codecName() == "UTF-8"); + //qDebug() << translator.codecName(); + bool fileIsUtf8 = (format == 20 || trIsUtf8); + + // The xml prolog allows processors to easily detect the correct encoding + t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n"; + + if (format == 11) + t << "<TS version=\"1.1\""; + else + t << "<TS version=\"2.0\""; + + QString languageCode = translator.languageCode(); + if (!languageCode.isEmpty() && languageCode != QLatin1String("C")) + t << " language=\"" << languageCode << "\""; + if (format == 20) { + languageCode = translator.sourceLanguageCode(); + if (!languageCode.isEmpty() && languageCode != QLatin1String("C")) + t << " sourcelanguage=\"" << languageCode << "\""; + } + t << ">\n"; + + QByteArray codecName = translator.codecName(); + if (codecName != "ISO-8859-1") + t << "<defaultcodec>" << codecName << "</defaultcodec>\n"; + + QRegExp drops(cd.dropTags().join(QLatin1String("|"))); + + if (format == 20) + writeExtras(t, " ", translator.extras(), drops); + + QHash<QString, QList<TranslatorMessage> > messageOrder; + QList<QString> contextOrder; + foreach (const TranslatorMessage &msg, translator.messages()) { + // no need for such noise + if (msg.type() == TranslatorMessage::Obsolete && msg.translation().isEmpty()) + continue; + + QList<TranslatorMessage> &context = messageOrder[msg.context()]; + if (context.isEmpty()) + contextOrder.append(msg.context()); + context.append(msg); + } + if (cd.sortContexts()) + qSort(contextOrder); + + QHash<QString, int> currentLine; + QString currentFile; + foreach (const QString &context, contextOrder) { + const TranslatorMessage &firstMsg = messageOrder[context].first(); + t << "<context" << ((!fileIsUtf8 && firstMsg.isUtf8()) ? " encoding=\"UTF-8\"" : "") << ">\n"; + + t << " <name>" + << evilBytes(context, firstMsg.isUtf8() || fileIsUtf8, format, codecName) + << "</name>\n"; + foreach (const TranslatorMessage &msg, messageOrder[context]) { + //msg.dump(); + + t << " <message"; + if (!msg.id().isEmpty()) + t << " id=\"" << msg.id() << "\""; + if (format == 11 && !trIsUtf8 && msg.isUtf8()) + t << " encoding=\"UTF-8\""; + if (format == 20 && !trIsUtf8 && msg.isUtf8()) + t << " utf8=\"true\""; + if (msg.isPlural()) + t << " numerus=\"yes\""; + t << ">\n"; + if (translator.locationsType() != Translator::NoLocations) { + QString cfile = currentFile; + bool first = true; + foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) { + QString fn = cd.m_targetDir.relativeFilePath(ref.fileName()) + .replace(QLatin1Char('\\'),QLatin1Char('/')); + int ln = ref.lineNumber(); + QString ld; + if (translator.locationsType() == Translator::RelativeLocations) { + if (ln != -1) { + int dlt = ln - currentLine[fn]; + if (dlt >= 0) + ld.append(QLatin1Char('+')); + ld.append(QString::number(dlt)); + currentLine[fn] = ln; + } + + if (fn != cfile) { + if (first) + currentFile = fn; + cfile = fn; + } else { + fn.clear(); + } + first = false; + } else { + if (ln != -1) + ld = QString::number(ln); + } + t << " <location"; + if (!fn.isEmpty()) + t << " filename=\"" << fn << "\""; + if (!ld.isEmpty()) + t << " line=\"" << ld << "\""; + t << "/>\n"; + } + } + + t << " <source>" + << evilBytes(msg.sourceText(), msg.isUtf8(), format, codecName) + << "</source>\n"; + + if (format != 11 && !msg.oldSourceText().isEmpty()) + t << " <oldsource>" << protect(msg.oldSourceText()) << "</oldsource>\n"; + + if (!msg.comment().isEmpty()) { + t << " <comment>" + << evilBytes(msg.comment(), msg.isUtf8(), format, codecName) + << "</comment>\n"; + } + + if (format != 11) { + + if (!msg.oldComment().isEmpty()) + t << " <oldcomment>" << protect(msg.oldComment()) << "</oldcomment>\n"; + + if (!msg.extraComment().isEmpty()) + t << " <extracomment>" << protect(msg.extraComment()) + << "</extracomment>\n"; + + if (!msg.translatorComment().isEmpty()) + t << " <translatorcomment>" << protect(msg.translatorComment()) + << "</translatorcomment>\n"; + + } + + t << " <translation"; + if (msg.type() == TranslatorMessage::Unfinished) + t << " type=\"unfinished\""; + else if (msg.type() == TranslatorMessage::Obsolete) + t << " type=\"obsolete\""; + if (msg.isPlural()) { + t << ">"; + QStringList translns = translator.normalizedTranslations(msg, cd, &result); + for (int j = 0; j < qMax(1, translns.count()); ++j) { + t << "\n <numerusform"; + writeVariants(t, " ", translns[j]); + t << "</numerusform>"; + } + t << "\n "; + } else { + writeVariants(t, " ", msg.translation()); + } + t << "</translation>\n"; + + if (format != 11) + writeExtras(t, " ", msg.extras(), drops); + + if (!msg.userData().isEmpty()) + t << " <userdata>" << msg.userData() << "</userdata>\n"; + t << " </message>\n"; + } + t << "</context>\n"; + } + + t << "</TS>\n"; + return result; +} + +bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + translator.setLocationsType(Translator::NoLocations); + TSReader reader(dev, cd); + return reader.read(translator); +} + +bool saveTS11(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + return saveTS(translator, dev, cd, 11); +} + +bool saveTS20(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + return saveTS(translator, dev, cd, 20); +} + +int initTS() +{ + Translator::FileFormat format; + + format.extension = QLatin1String("ts11"); + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = -1; + format.description = QObject::tr("Qt translation sources (format 1.1)"); + format.loader = &loadTS; + format.saver = &saveTS11; + Translator::registerFileFormat(format); + + format.extension = QLatin1String("ts20"); + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = -1; + format.description = QObject::tr("Qt translation sources (format 2.0)"); + format.loader = &loadTS; + format.saver = &saveTS20; + Translator::registerFileFormat(format); + + // "ts" is always the latest. right now it's ts20. + format.extension = QLatin1String("ts"); + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = 0; + format.description = QObject::tr("Qt translation sources (latest format)"); + format.loader = &loadTS; + format.saver = &saveTS20; + Translator::registerFileFormat(format); + + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initTS) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/ts.dtd b/tools/linguist/shared/ts.dtd new file mode 100644 index 0000000..ab77f64 --- /dev/null +++ b/tools/linguist/shared/ts.dtd @@ -0,0 +1,113 @@ +<!-- + ! + ! Some notes to the DTD: + ! + ! The location element is set as optional since it was introduced first in Qt 4.2. + ! The userdata element is set as optional since it was introduced first in Qt 4.4. + ! The source and translation elements are optional starting with version 3.0 + ! (Qt 4.6) to support S60 blank messages. + ! + --> +<!-- + ! Macro used in order to escape byte entities not allowed in an xml document + ! for instance, only #x9, #xA and #xD are allowed characters below #x20. + --> +<!ENTITY % evilstring '(#PCDATA | byte)*' > +<!ELEMENT byte EMPTY> +<!-- value contains decimal (e.g. 1000) or hex (e.g. x3e8) unicode encoding of one char --> +<!ATTLIST byte + value CDATA #REQUIRED> +<!-- + ! This element wildcard is no valid DTD. No better solution available. + ! extra elements may appear in TS and message elements. Each element may appear + ! only once within each scope. The contents are preserved verbatim; any + ! attributes are dropped. Currently recognized extra tags include: + ! extra-po-msgid_plural, extra-po-old_msgid_plural + ! extra-po-flags (comma-space separated list) + ! extra-loc-layout_id + ! extra-loc-feature + ! extra-loc-blank + --> +<!ELEMENT extra-* %evilstring; > +<!ELEMENT TS (defaultcodec?, extra-**, (context|message)+) > +<!ATTLIST TS + version CDATA #IMPLIED + sourcelanguage CDATA #IMPLIED + language CDATA #IMPLIED> +<!-- The encoding to use in the .qm file by default. Default is ISO-8859-1. --> +<!ELEMENT defaultcodec (#PCDATA) > +<!ELEMENT context (name?, comment?, (context|message)+) > +<!ATTLIST context + encoding CDATA #IMPLIED> +<!ELEMENT name %evilstring; > +<!-- If "no", then the context nesting is for informational puposes only --> +<!ATTLIST name + nest (yes|no) "yes"> +<!-- This is "disambiguation" in the (new) API, or "msgctxt" in gettext speak --> +<!ELEMENT comment %evilstring; > +<!-- Previous content of comment (result of merge) --> +<!ELEMENT oldcomment %evilstring; > +<!-- The real comment (added by developer/designer) --> +<!ELEMENT extracomment %evilstring; > +<!-- Comment added by translator --> +<!ELEMENT translatorcomment %evilstring; > +<!ELEMENT message (location*, source?, oldsource?, comment?, oldcomment?, extracomment?, translatorcomment?, translation?, userdata?, extra-**) > +<!-- + ! If utf8 is true, the defaultcodec is overridden and the message is encoded + ! in UTF-8 in the .qm file. + --> +<!ATTLIST message + id CDATA #IMPLIED + utf8 (true|false) "false" + numerus (yes|no) "no"> +<!ELEMENT location EMPTY> +<!-- + ! If the line is omitted, the location specifies only a file. + ! + ! location supports relative specifications as well. Line numbers are + ! relative (explicitly positive or negative) to the last reference to a + ! given filename; each file starts with current line 0. If the filename + ! is omitted, the "current" one is used. For the 1st location in a message, + ! "current" is the filename used for the 1st location of the previous message. + ! For subsequent locations, it is the filename used for the previous location. + ! A single .ts file has either all absolute or all relative locations. + --> +<!ATTLIST location + filename CDATA #IMPLIED + line CDATA #IMPLIED> +<!ELEMENT source %evilstring;> +<!-- Previous content of source (result of merge) --> +<!ELEMENT oldsource %evilstring;> +<!-- + ! The following should really say one evilstring macro or several + ! numerusform or lengthvariant elements, but the DTD can't express this. + --> +<!ELEMENT translation (#PCDATA|byte|numerusform|lengthvariant)* > +<!-- + ! If no type is set, the message is "finished". + ! Length variants must be ordered by falling display length. + ! variants may not be yes if the message has numerus yes. + --> +<!ATTLIST translation + type (unfinished|obsolete) #IMPLIED + variants (yes|no) "no"> +<!-- Deprecated. Use extra-* --> +<!ELEMENT userdata (#PCDATA)* > +<!-- + ! The following should really say one evilstring macro or several + ! lengthvariant elements, but the DTD can't express this. + ! Length variants must be ordered by falling display length. + --> +<!ELEMENT numerusform (#PCDATA|byte|lengthvariant)* > +<!ATTLIST numerusform + plurality (nullar|singular|dual|trial|paucal|greaterpaucal|plural|greaterplural) #IMPLIED> + variants (yes|no) "no"> +<!ELEMENT lengthvariant %evilstring; > +<!-- + ! The translation variants have a priority between 1 ("highest") and 9 ("lowest") + ! Typically longer translations get a higher priority. + ! If omitted, the order of appearance of the variants in the .ts files is used. + --> +<!ATTLIST lengthvariant + priority (1|2|3|4|5|6|7|8|9) #IMPLIED> + diff --git a/tools/linguist/shared/ui.cpp b/tools/linguist/shared/ui.cpp new file mode 100644 index 0000000..4b86714 --- /dev/null +++ b/tools/linguist/shared/ui.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtCore/QString> + +#include <QtXml/QXmlAttributes> +#include <QtXml/QXmlDefaultHandler> +#include <QtXml/QXmlLocator> +#include <QtXml/QXmlParseException> + + +QT_BEGIN_NAMESPACE + +// in cpp.cpp +void fetchtrInlinedCpp(const QString &in, Translator &tor, const QString &context); + +class UiReader : public QXmlDefaultHandler +{ +public: + UiReader(Translator &translator, ConversionData &cd) + : m_translator(translator), m_cd(cd), m_lineNumber(-1), + m_needUtf8(translator.codecName() != "UTF-8") + {} + + 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; } + +private: + void flush(); + + Translator &m_translator; + ConversionData &m_cd; + QString m_context; + QString m_source; + QString m_comment; + QXmlLocator *m_locator; + + QString m_accum; + int m_lineNumber; + bool m_isTrString; + bool m_needUtf8; +}; + +bool UiReader::startElement(const QString &namespaceURI, + const QString &localName, const QString &qName, const QXmlAttributes &atts) +{ + Q_UNUSED(namespaceURI); + Q_UNUSED(localName); + + if (qName == QLatin1String("item")) { + flush(); + if (!atts.value(QLatin1String("text")).isEmpty()) + m_source = atts.value(QLatin1String("text")); + } else if (qName == QLatin1String("string")) { + flush(); + if (atts.value(QLatin1String("notr")).isEmpty() || + atts.value(QLatin1String("notr")) != QLatin1String("true")) { + m_isTrString = true; + m_comment = atts.value(QLatin1String("comment")); + } else { + m_isTrString = false; + } + } + if (m_isTrString && !m_cd.m_noUiLines) + m_lineNumber = m_locator->lineNumber(); + m_accum.clear(); + return true; +} + +bool UiReader::endElement(const QString &namespaceURI, + const QString &localName, const QString &qName) +{ + Q_UNUSED(namespaceURI); + Q_UNUSED(localName); + + m_accum.replace(QLatin1String("\r\n"), QLatin1String("\n")); + + if (qName == QLatin1String("class")) { + if (m_context.isEmpty()) + m_context = m_accum; + } else if (qName == QLatin1String("string") && m_isTrString) { + m_source = m_accum; + } else if (qName == QLatin1String("comment")) { + m_comment = m_accum; + flush(); + } else if (qName == QLatin1String("function")) { + fetchtrInlinedCpp(m_accum, m_translator, m_context); + } else { + flush(); + } + return true; +} + +bool UiReader::characters(const QString &ch) +{ + m_accum += ch; + return true; +} + +bool UiReader::fatalError(const QXmlParseException &exception) +{ + QString msg; + msg.sprintf("XML error: Parse error at line %d, column %d (%s).", + exception.lineNumber(), exception.columnNumber(), + exception.message().toLatin1().data()); + m_cd.appendError(msg); + return false; +} + +void UiReader::flush() +{ + if (!m_context.isEmpty() && !m_source.isEmpty()) { + TranslatorMessage msg(m_context, m_source, + m_comment, QString(), m_cd.m_sourceFileName, + m_lineNumber, QStringList()); + if (m_needUtf8 && msg.needs8Bit()) + msg.setUtf8(true); + m_translator.extend(msg); + } + m_source.clear(); + m_comment.clear(); +} + +bool loadUI(Translator &translator, QIODevice &dev, ConversionData &cd) +{ + QXmlInputSource in(&dev); + 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); + if (!result) + cd.appendError(QLatin1String("Parse error in UI file")); + reader.setContentHandler(0); + reader.setErrorHandler(0); + return result; +} + +bool saveUI(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + Q_UNUSED(dev); + Q_UNUSED(translator); + cd.appendError(QLatin1String("Cannot save .ui files")); + return false; +} + +int initUI() +{ + Translator::FileFormat format; + + // "real" Qt Designer + format.extension = QLatin1String("ui"); + format.description = QObject::tr("Qt Designer form files"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.loader = &loadUI; + format.saver = &saveUI; + Translator::registerFileFormat(format); + + // same for jambi + format.extension = QLatin1String("jui"); + format.description = QObject::tr("Qt Jambi form files"); + format.fileType = Translator::FileFormat::SourceCode; + format.priority = 0; + format.loader = &loadUI; + format.saver = &saveUI; + Translator::registerFileFormat(format); + + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initUI) + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/xliff.cpp b/tools/linguist/shared/xliff.cpp new file mode 100644 index 0000000..6acf67a --- /dev/null +++ b/tools/linguist/shared/xliff.cpp @@ -0,0 +1,828 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "translator.h" + +#include <QtCore/QDebug> +#include <QtCore/QMap> +#include <QtCore/QStack> +#include <QtCore/QString> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <QtXml/QXmlAttributes> +#include <QtXml/QXmlDefaultHandler> +#include <QtXml/QXmlParseException> + + +QT_BEGIN_NAMESPACE + +/** + * Implementation of XLIFF file format for Linguist + */ +//static const char *restypeDomain = "x-gettext-domain"; +static const char *restypeContext = "x-trolltech-linguist-context"; +static const char *restypePlurals = "x-gettext-plurals"; +static const char *restypeDummy = "x-dummy"; +static const char *dataTypeUIFile = "x-trolltech-designer-ui"; +static const char *contextMsgctxt = "x-gettext-msgctxt"; // XXX Troll invention, so far. +static const char *contextOldMsgctxt = "x-gettext-previous-msgctxt"; // XXX Troll invention, so far. +static const char *attribPlural = "trolltech:plural"; +static const char *XLIFF11namespaceURI = "urn:oasis:names:tc:xliff:document:1.1"; +static const char *XLIFF12namespaceURI = "urn:oasis:names:tc:xliff:document:1.2"; +static const char *TrollTsNamespaceURI = "urn:trolltech:names:ts:document:1.0"; + +#define COMBINE4CHARS(c1, c2, c3, c4) \ + (int(c1) << 24 | int(c2) << 16 | int(c3) << 8 | int(c4) ) + +static QString dataType(const TranslatorMessage &m) +{ + QByteArray fileName = m.fileName().toAscii(); + unsigned int extHash = 0; + int pos = fileName.count() - 1; + for (int pass = 0; pass < 4 && pos >=0; ++pass, --pos) { + if (fileName.at(pos) == '.') + break; + extHash |= ((int)fileName.at(pos) << (8*pass)); + } + + switch (extHash) { + case COMBINE4CHARS(0,'c','p','p'): + case COMBINE4CHARS(0,'c','x','x'): + case COMBINE4CHARS(0,'c','+','+'): + case COMBINE4CHARS(0,'h','p','p'): + case COMBINE4CHARS(0,'h','x','x'): + case COMBINE4CHARS(0,'h','+','+'): + return QLatin1String("cpp"); + case COMBINE4CHARS(0, 0 , 0 ,'c'): + case COMBINE4CHARS(0, 0 , 0 ,'h'): + case COMBINE4CHARS(0, 0 ,'c','c'): + case COMBINE4CHARS(0, 0 ,'c','h'): + case COMBINE4CHARS(0, 0 ,'h','h'): + return QLatin1String("c"); + case COMBINE4CHARS(0, 0 ,'u','i'): + return QLatin1String(dataTypeUIFile); //### form? + default: + return QLatin1String("plaintext"); // we give up + } +} + +static void writeIndent(QTextStream &ts, int indent) +{ + ts << QString().fill(QLatin1Char(' '), indent * 2); +} + +struct CharMnemonic +{ + char ch; + char escape; + const char *mnemonic; +}; + +static const CharMnemonic charCodeMnemonics[] = { + {0x07, 'a', "bel"}, + {0x08, 'b', "bs"}, + {0x09, 't', "tab"}, + {0x0a, 'n', "lf"}, + {0x0b, 'v', "vt"}, + {0x0c, 'f', "ff"}, + {0x0d, 'r', "cr"} +}; + +static char charFromEscape(char escape) +{ + for (uint i = 0; i < sizeof(charCodeMnemonics)/sizeof(CharMnemonic); ++i) { + CharMnemonic cm = charCodeMnemonics[i]; + if (cm.escape == escape) + return cm.ch; + } + Q_ASSERT(0); + return escape; +} + +static QString numericEntity(int ch, bool makePhs) +{ + // ### This needs to be reviewed, to reflect the updated XLIFF-PO spec. + if (!makePhs || ch < 7 || ch > 0x0d) + return QString::fromAscii("&#x%1;").arg(QString::number(ch, 16)); + + CharMnemonic cm = charCodeMnemonics[int(ch) - 7]; + QString name = QLatin1String(cm.mnemonic); + char escapechar = cm.escape; + + static int id = 0; + return QString::fromAscii("<ph id=\"ph%1\" ctype=\"x-ch-%2\">\\%3</ph>") + .arg(++id) .arg(name) .arg(escapechar); +} + +static QString protect(const QString &str, bool makePhs = true) +{ + QString result; + int len = str.size(); + for (int i = 0; i != len; ++i) { + uint c = str.at(i).unicode(); + switch (c) { + case '\"': + result += QLatin1String("""); + break; + case '&': + result += QLatin1String("&"); + break; + case '>': + result += QLatin1String(">"); + break; + case '<': + result += QLatin1String("<"); + break; + case '\'': + result += QLatin1String("'"); + break; + default: + if (c < 0x20 && c != '\r' && c != '\n' && c != '\t') + result += numericEntity(c, makePhs); + else // this also covers surrogates + result += QChar(c); + } + } + return result; +} + + +static void writeExtras(QTextStream &ts, int indent, + const TranslatorMessage::ExtraData &extras, const QRegExp &drops) +{ + for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) { + if (!drops.exactMatch(it.key())) { + writeIndent(ts, indent); + ts << "<trolltech:" << it.key() << '>' + << protect(it.value()) + << "</trolltech:" << it.key() << ">\n"; + } + } +} + +static void writeLineNumber(QTextStream &ts, const TranslatorMessage &msg, int indent) +{ + if (msg.lineNumber() == -1) + return; + writeIndent(ts, indent); + ts << "<context-group purpose=\"location\"><context context-type=\"linenumber\">" + << msg.lineNumber() << "</context></context-group>\n"; + foreach (const TranslatorMessage::Reference &ref, msg.extraReferences()) { + writeIndent(ts, indent); + ts << "<context-group purpose=\"location\">"; + if (ref.fileName() != msg.fileName()) + ts << "<context context-type=\"sourcefile\">" << ref.fileName() << "</context>"; + ts << "<context context-type=\"linenumber\">" << ref.lineNumber() + << "</context></context-group>\n"; + } +} + +static void writeComment(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent) +{ + if (!msg.comment().isEmpty()) { + writeIndent(ts, indent); + ts << "<context-group><context context-type=\"" << contextMsgctxt << "\">" + << protect(msg.comment(), false) + << "</context></context-group>\n"; + } + if (!msg.oldComment().isEmpty()) { + writeIndent(ts, indent); + ts << "<context-group><context context-type=\"" << contextOldMsgctxt << "\">" + << protect(msg.oldComment(), false) + << "</context></context-group>\n"; + } + writeExtras(ts, indent, msg.extras(), drops); + if (!msg.extraComment().isEmpty()) { + writeIndent(ts, indent); + ts << "<note annotates=\"source\" from=\"developer\">" + << protect(msg.extraComment()) << "</note>\n"; + } + if (!msg.translatorComment().isEmpty()) { + writeIndent(ts, indent); + ts << "<note from=\"translator\">" + << protect(msg.translatorComment()) << "</note>\n"; + } +} + +static void writeTransUnits(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent, + const Translator &translator, ConversionData &cd, bool *ok) +{ + static int msgid; + QString msgidstr = !msg.id().isEmpty() ? msg.id() : QString::fromAscii("_msg%1").arg(++msgid); + + QStringList translns = translator.normalizedTranslations(msg, cd, ok); + QHash<QString, QString>::const_iterator it; + QString pluralStr; + QStringList sources(msg.sourceText()); + if ((it = msg.extras().find(QString::fromLatin1("po-msgid_plural"))) != msg.extras().end()) + sources.append(*it); + QStringList oldsources; + if (!msg.oldSourceText().isEmpty()) + oldsources.append(msg.oldSourceText()); + if ((it = msg.extras().find(QString::fromLatin1("po-old_msgid_plural"))) != msg.extras().end()) { + if (oldsources.isEmpty()) { + if (sources.count() == 2) + oldsources.append(QString()); + else + pluralStr = QLatin1Char(' ') + QLatin1String(attribPlural) + QLatin1String("=\"yes\""); + } + oldsources.append(*it); + } + + QStringList::const_iterator + srcit = sources.begin(), srcend = sources.end(), + oldsrcit = oldsources.begin(), oldsrcend = oldsources.end(), + transit = translns.begin(), transend = translns.end(); + int plural = 0; + QString source; + while (srcit != srcend || oldsrcit != oldsrcend || transit != transend) { + QByteArray attribs; + QByteArray state; + if (msg.type() == TranslatorMessage::Obsolete) { + if (!msg.isPlural()) + attribs = " translate=\"no\""; + } else if (msg.type() == TranslatorMessage::Finished) { + attribs = " approved=\"yes\""; + } else if (transit != transend && !transit->isEmpty()) { + state = " state=\"needs-review-translation\""; + } + writeIndent(ts, indent); + ts << "<trans-unit id=\"" << msgidstr; + if (msg.isPlural()) + ts << "[" << plural++ << "]"; + ts << "\"" << attribs << ">\n"; + ++indent; + + writeIndent(ts, indent); + if (srcit != srcend) { + source = *srcit; + ++srcit; + } // else just repeat last element + ts << "<source xml:space=\"preserve\">" << protect(source) << "</source>\n"; + + bool puttrans = false; + QString translation; + if (transit != transend) { + translation = *transit; + ++transit; + puttrans = true; + } + do { + if (oldsrcit != oldsrcend && !oldsrcit->isEmpty()) { + writeIndent(ts, indent); + ts << "<alt-trans>\n"; + ++indent; + writeIndent(ts, indent); + ts << "<source xml:space=\"preserve\"" << pluralStr << '>' << protect(*oldsrcit) << "</source>\n"; + if (!puttrans) { + writeIndent(ts, indent); + ts << "<target restype=\"" << restypeDummy << "\"/>\n"; + } + } + + if (puttrans) { + writeIndent(ts, indent); + ts << "<target xml:space=\"preserve\"" << state << ">" << protect(translation) << "</target>\n"; + } + + if (oldsrcit != oldsrcend) { + if (!oldsrcit->isEmpty()) { + --indent; + writeIndent(ts, indent); + ts << "</alt-trans>\n"; + } + ++oldsrcit; + } + + puttrans = false; + } while (srcit == srcend && oldsrcit != oldsrcend); + + if (!msg.isPlural()) { + writeLineNumber(ts, msg, indent); + writeComment(ts, msg, drops, indent); + } + + --indent; + writeIndent(ts, indent); + ts << "</trans-unit>\n"; + } +} + +static void writeMessage(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent, + const Translator &translator, ConversionData &cd, bool *ok) +{ + if (msg.isPlural()) { + writeIndent(ts, indent); + ts << "<group restype=\"" << restypePlurals << "\""; + if (!msg.id().isEmpty()) + ts << " id=\"" << msg.id() << "\""; + if (msg.type() == TranslatorMessage::Obsolete) + ts << " translate=\"no\""; + ts << ">\n"; + ++indent; + writeLineNumber(ts, msg, indent); + writeComment(ts, msg, drops, indent); + + writeTransUnits(ts, msg, drops, indent, translator, cd, ok); + --indent; + writeIndent(ts, indent); + ts << "</group>\n"; + } else { + writeTransUnits(ts, msg, drops, indent, translator, cd, ok); + } +} + + +class XLIFFHandler : public QXmlDefaultHandler +{ +public: + XLIFFHandler(Translator &translator, ConversionData &cd); + + 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); + + bool endDocument(); + +private: + enum XliffContext { + XC_xliff, + XC_group, + XC_trans_unit, + XC_context_group, + XC_context_group_any, + XC_context, + XC_context_filename, + XC_context_linenumber, + XC_context_context, + XC_context_comment, + XC_context_old_comment, + XC_ph, + XC_extra_comment, + XC_translator_comment, + XC_restype_context, + XC_restype_translation, + XC_restype_plurals, + XC_alt_trans + }; + void pushContext(XliffContext ctx); + bool popContext(XliffContext ctx); + XliffContext currentContext() const; + bool hasContext(XliffContext ctx) const; + bool finalizeMessage(bool isPlural); + +private: + Translator &m_translator; + ConversionData &m_cd; + TranslatorMessage::Type m_type; + QString m_language; + QString m_sourceLanguage; + QString m_context; + QString m_id; + QStringList m_sources; + QStringList m_oldSources; + QString m_comment; + QString m_oldComment; + QString m_extraComment; + QString m_translatorComment; + bool m_isPlural; + bool m_hadAlt; + QStringList m_translations; + QString m_fileName; + int m_lineNumber; + QString m_extraFileName; + TranslatorMessage::References m_refs; + TranslatorMessage::ExtraData m_extra; + + QString accum; + QString m_ctype; + const QString m_URITT; // convenience and efficiency + const QString m_URI; // ... + const QString m_URI12; // ... + QStack<int> m_contextStack; +}; + +XLIFFHandler::XLIFFHandler(Translator &translator, ConversionData &cd) + : m_translator(translator), m_cd(cd), + m_type(TranslatorMessage::Finished), + m_lineNumber(-1), + m_URITT(QLatin1String(TrollTsNamespaceURI)), + m_URI(QLatin1String(XLIFF11namespaceURI)), + m_URI12(QLatin1String(XLIFF12namespaceURI)) +{} + + +void XLIFFHandler::pushContext(XliffContext ctx) +{ + m_contextStack.push_back(ctx); +} + +// Only pops it off if the top of the stack contains ctx +bool XLIFFHandler::popContext(XliffContext ctx) +{ + if (!m_contextStack.isEmpty() && m_contextStack.top() == ctx) { + m_contextStack.pop(); + return true; + } + return false; +} + +XLIFFHandler::XliffContext XLIFFHandler::currentContext() const +{ + if (!m_contextStack.isEmpty()) + return (XliffContext)m_contextStack.top(); + return XC_xliff; +} + +// traverses to the top to check all of the parent contexes. +bool XLIFFHandler::hasContext(XliffContext ctx) const +{ + for (int i = m_contextStack.count() - 1; i >= 0; --i) { + if (m_contextStack.at(i) == ctx) + return true; + } + return false; +} + +bool XLIFFHandler::startElement(const QString& namespaceURI, + const QString &localName, const QString &qName, const QXmlAttributes &atts ) +{ + Q_UNUSED(qName); + if (namespaceURI == m_URITT) + goto bail; + if (namespaceURI != m_URI && namespaceURI != m_URI12) + return false; + 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_sourceLanguage = atts.value(QLatin1String("source-language")); + } else if (localName == QLatin1String("group")) { + if (atts.value(QLatin1String("restype")) == QLatin1String(restypeContext)) { + m_context = atts.value(QLatin1String("resname")); + pushContext(XC_restype_context); + } else { + if (atts.value(QLatin1String("restype")) == QLatin1String(restypePlurals)) { + pushContext(XC_restype_plurals); + m_id = atts.value(QLatin1String("id")); + if (atts.value(QLatin1String("translate")) == QLatin1String("no")) + m_type = TranslatorMessage::Obsolete; + } else { + pushContext(XC_group); + } + } + } else if (localName == QLatin1String("trans-unit")) { + if (!hasContext(XC_restype_plurals) || m_sources.isEmpty() /* who knows ... */) + if (atts.value(QLatin1String("translate")) == QLatin1String("no")) + m_type = TranslatorMessage::Obsolete; + if (!hasContext(XC_restype_plurals)) { + m_id = atts.value(QLatin1String("id")); + if (m_id.startsWith(QLatin1String("_msg"))) + m_id.clear(); + } + if (m_type != TranslatorMessage::Obsolete && + atts.value(QLatin1String("approved")) != QLatin1String("yes")) + m_type = TranslatorMessage::Unfinished; + pushContext(XC_trans_unit); + m_hadAlt = false; + } else if (localName == QLatin1String("alt-trans")) { + pushContext(XC_alt_trans); + } else if (localName == QLatin1String("source")) { + m_isPlural = atts.value(QLatin1String(attribPlural)) == QLatin1String("yes"); + } else if (localName == QLatin1String("target")) { + 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")) + 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")); + 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")); + if (ctxtype == QLatin1String(contextMsgctxt)) + pushContext(XC_context_comment); + else if (ctxtype == QLatin1String(contextOldMsgctxt)) + pushContext(XC_context_old_comment); + } else if (localName == QLatin1String("note")) { + if (atts.value(QLatin1String("annotates")) == QLatin1String("source") && + atts.value(QLatin1String("from")) == QLatin1String("developer")) + pushContext(XC_extra_comment); + else + pushContext(XC_translator_comment); + } else if (localName == QLatin1String("ph")) { + QString ctype = atts.value(QLatin1String("ctype")); + if (ctype.startsWith(QLatin1String("x-ch-"))) + m_ctype = ctype.mid(5); + pushContext(XC_ph); + } +bail: + if (currentContext() != XC_ph) + accum.clear(); + return true; +} + +bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localName, + const QString &qName) +{ + Q_UNUSED(qName); + if (namespaceURI == m_URITT) { + if (hasContext(XC_trans_unit) || hasContext(XC_restype_plurals)) + m_extra[localName] = accum; + else + m_translator.setExtra(localName, accum); + return true; + } + if (namespaceURI != m_URI && namespaceURI != m_URI12) + return false; + //qDebug() << "URI:" << namespaceURI << "QNAME:" << qName; + if (localName == QLatin1String("xliff")) { + popContext(XC_xliff); + } else if (localName == QLatin1String("source")) { + if (hasContext(XC_alt_trans)) { + if (m_isPlural && m_oldSources.isEmpty()) + m_oldSources.append(QString()); + m_oldSources.append(accum); + m_hadAlt = true; + } else { + m_sources.append(accum); + } + } else if (localName == QLatin1String("target")) { + if (popContext(XC_restype_translation)) + m_translations.append(accum); + } else if (localName == QLatin1String("context-group")) { + if (popContext(XC_context_group)) { + m_refs.append(TranslatorMessage::Reference( + m_extraFileName.isEmpty() ? m_fileName : m_extraFileName, m_lineNumber)); + m_extraFileName.clear(); + m_lineNumber = -1; + } else { + popContext(XC_context_group_any); + } + } else if (localName == QLatin1String("context")) { + if (popContext(XC_context_linenumber)) { + bool ok; + m_lineNumber = accum.trimmed().toInt(&ok); + if (!ok) + m_lineNumber = -1; + } else if (popContext(XC_context_filename)) { + m_extraFileName = accum; + } else if (popContext(XC_context_comment)) { + m_comment = accum; + } else if (popContext(XC_context_old_comment)) { + m_oldComment = accum; + } + } else if (localName == QLatin1String("note")) { + if (popContext(XC_extra_comment)) + m_extraComment = accum; + else if (popContext(XC_translator_comment)) + m_translatorComment = accum; + } else if (localName == QLatin1String("ph")) { + m_ctype.clear(); + popContext(XC_ph); + } else if (localName == QLatin1String("trans-unit")) { + popContext(XC_trans_unit); + if (!m_hadAlt) + m_oldSources.append(QString()); + if (!hasContext(XC_restype_plurals)) { + if (!finalizeMessage(false)) + return false; + } + } 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; + } else if (popContext(XC_restype_context)) { + m_context.clear(); + } else { + popContext(XC_group); + } + } + return true; +} + +bool XLIFFHandler::characters(const QString &ch) +{ + if (currentContext() == XC_ph) { + // handle the content of <ph> elements + for (int i = 0; i < ch.count(); ++i) { + QChar chr = ch.at(i); + if (accum.endsWith(QLatin1Char('\\'))) + accum[accum.size() - 1] = QLatin1Char(charFromEscape(chr.toAscii())); + else + accum.append(chr); + } + } else { + QString t = ch; + t.replace(QLatin1String("\r"), QLatin1String("")); + accum.append(t); + } + return true; +} + +bool XLIFFHandler::endDocument() +{ + m_translator.setLanguageCode(m_language); + m_translator.setSourceLanguageCode(m_sourceLanguage); + return true; +} + +bool XLIFFHandler::finalizeMessage(bool isPlural) +{ + if (m_sources.isEmpty()) { + m_cd.appendError(QLatin1String("XLIFF syntax error: Message without source string.")); + return false; + } + TranslatorMessage msg(m_context, m_sources[0], + m_comment, QString(), QString(), -1, + m_translations, m_type, isPlural); + msg.setId(m_id); + msg.setReferences(m_refs); + msg.setOldComment(m_oldComment); + msg.setExtraComment(m_extraComment); + msg.setTranslatorComment(m_translatorComment); + if (m_sources.count() > 1 && m_sources[1] != m_sources[0]) + m_extra.insert(QLatin1String("po-msgid_plural"), m_sources[1]); + if (!m_oldSources.isEmpty()) { + if (!m_oldSources[0].isEmpty()) + msg.setOldSourceText(m_oldSources[0]); + if (m_oldSources.count() > 1 && m_oldSources[1] != m_oldSources[0]) + m_extra.insert(QLatin1String("po-old_msgid_plural"), m_oldSources[1]); + } + msg.setExtras(m_extra); + m_translator.append(msg); + + m_id.clear(); + m_sources.clear(); + m_oldSources.clear(); + m_translations.clear(); + m_comment.clear(); + m_oldComment.clear(); + m_extraComment.clear(); + m_translatorComment.clear(); + m_extra.clear(); + m_refs.clear(); + m_type = TranslatorMessage::Finished; + return true; +} + +bool XLIFFHandler::fatalError(const QXmlParseException &exception) +{ + QString msg; + msg.sprintf("XML error: Parse error at line %d, column %d (%s).\n", + exception.lineNumber(), exception.columnNumber(), + exception.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); +} + +bool saveXLIFF(const Translator &translator, QIODevice &dev, ConversionData &cd) +{ + bool ok = true; + int indent = 0; + + QTextStream ts(&dev); + ts.setCodec(QTextCodec::codecForName("UTF-8")); + + QStringList dtgs = cd.dropTags(); + dtgs << QLatin1String("po-(old_)?msgid_plural"); + QRegExp drops(dtgs.join(QLatin1String("|"))); + + QHash<QString, QHash<QString, QList<TranslatorMessage> > > messageOrder; + QHash<QString, QList<QString> > contextOrder; + QList<QString> fileOrder; + foreach (const TranslatorMessage &msg, translator.messages()) { + QHash<QString, QList<TranslatorMessage> > &file = messageOrder[msg.fileName()]; + if (file.isEmpty()) + fileOrder.append(msg.fileName()); + QList<TranslatorMessage> &context = file[msg.context()]; + if (context.isEmpty()) + contextOrder[msg.fileName()].append(msg.context()); + context.append(msg); + } + + ts.setFieldAlignment(QTextStream::AlignRight); + ts << "<?xml version=\"1.0\""; + ts << " encoding=\"utf-8\"?>\n"; + ts << "<xliff version=\"1.2\" xmlns=\"" << XLIFF12namespaceURI + << "\" xmlns:trolltech=\"" << TrollTsNamespaceURI << "\">\n"; + ++indent; + writeExtras(ts, indent, translator.extras(), drops); + foreach (const QString &fn, fileOrder) { + writeIndent(ts, indent); + ts << "<file original=\"" << fn << "\"" + << " datatype=\"" << dataType(messageOrder[fn].begin()->first()) << "\"" + << " source-language=\"" + << (translator.sourceLanguageCode().isEmpty() ? + QByteArray("en") : translator.sourceLanguageCode().toLatin1()) << "\"" + << " target-language=\"" << translator.languageCode() << "\"" + << "><body>\n"; + ++indent; + + foreach (const QString &ctx, contextOrder[fn]) { + if (!ctx.isEmpty()) { + writeIndent(ts, indent); + ts << "<group restype=\"" << restypeContext << "\"" + << " resname=\"" << protect(ctx) << "\">\n"; + ++indent; + } + + foreach (const TranslatorMessage &msg, messageOrder[fn][ctx]) + writeMessage(ts, msg, drops, indent, translator, cd, &ok); + + if (!ctx.isEmpty()) { + --indent; + writeIndent(ts, indent); + ts << "</group>\n"; + } + } + + --indent; + writeIndent(ts, indent); + ts << "</body></file>\n"; + } + --indent; + writeIndent(ts, indent); + ts << "</xliff>\n"; + + return ok; +} + +int initXLIFF() +{ + Translator::FileFormat format; + format.extension = QLatin1String("xlf"); + format.description = QObject::tr("XLIFF localization files"); + format.fileType = Translator::FileFormat::TranslationSource; + format.priority = 1; + format.loader = &loadXLIFF; + format.saver = &saveXLIFF; + Translator::registerFileFormat(format); + return 1; +} + +Q_CONSTRUCTOR_FUNCTION(initXLIFF) + +QT_END_NAMESPACE |