blob: 4ddccd13b1f711b5feb97ab7c692f5b351953bea [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Linguist of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "lupdate.h"
#include <translator.h>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <QtCore/QStack>
#include <QtCore/QStack>
#include <QtCore/QString>
#include <QtCore/QTextCodec>
#include <QtCore/QCoreApplication>
#include <iostream>
#include <ctype.h>
QT_BEGIN_NAMESPACE
class LU {
Q_DECLARE_TR_FUNCTIONS(LUpdate)
};
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, Tok_null };
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;
std::ostream &yyMsg(int line = 0)
{
return std::cerr << qPrintable(yyFileName) << ':' << (line ? line : yyLineNo) << ": ";
}
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;
case 'n':
if ( yyIdent == QLatin1String("null") )
return Tok_null;
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 ) {
yyMsg() << qPrintable(LU::tr("Unterminated Java comment.\n"));
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) {
yyMsg() << qPrintable(LU::tr("Invalid Unicode value.\n"));
break;
}
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('"') )
yyMsg() << qPrintable(LU::tr("Unterminated string.\n"));
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 {
yyMsg() << qPrintable(LU::tr(
"String used in translation can contain only literals"
" concatenated with other literals, not expressions or numbers.\n"));
return false;
}
yyTok = getToken();
}
return true;
}
static bool matchStringOrNull(QString &s)
{
bool matches = matchString(s);
if (!matches) {
matches = (yyTok == Tok_null);
if (matches)
yyTok = getToken();
}
return matches;
}
/*
* 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 {
yyMsg() << qPrintable(LU::tr("'class' must be followed by a class name.\n"));
break;
}
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() ) {
yyMsg() << qPrintable(LU::tr("Excess closing brace.\n"));
}
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:
yyMsg() << qPrintable(LU::tr("'package' must be followed by package name.\n"));
break;
}
yyTok = getToken();
}
break;
default:
yyTok = getToken();
}
}
if ( !yyScope.isEmpty() )
yyMsg(yyScope.top()->line) << qPrintable(LU::tr("Unbalanced opening brace.\n"));
else if ( yyParenDepth != 0 )
yyMsg(yyParenLineNo) << qPrintable(LU::tr("Unbalanced opening parenthesis.\n"));
}
bool loadJava(Translator &translator, const QString &filename, ConversionData &cd)
{
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString()));
return false;
}
yyDefaultContext = cd.m_defaultContext;
yyInPos = -1;
yyFileName = filename;
yyPackage.clear();
yyScope.clear();
yyTok = -1;
yyParenDepth = 0;
yyCurLineNo = 0;
yyParenLineNo = 1;
QTextStream ts(&file);
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 = filename;
yyCurLineNo = 1;
yyParenLineNo = 1;
parse(&translator);
// Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it.
translator.setCodecName("UTF-8");
return true;
}
QT_END_NAMESPACE