diff options
Diffstat (limited to 'tools/linguist/lupdate/fetchtr.cpp')
-rw-r--r-- | tools/linguist/lupdate/fetchtr.cpp | 824 |
1 files changed, 824 insertions, 0 deletions
diff --git a/tools/linguist/lupdate/fetchtr.cpp b/tools/linguist/lupdate/fetchtr.cpp new file mode 100644 index 0000000..ce8fb98 --- /dev/null +++ b/tools/linguist/lupdate/fetchtr.cpp @@ -0,0 +1,824 @@ +/********************************************************************** +** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved. +** +** This file is part of Qt Linguist. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free Qt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing requirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at sales@trolltech.com. +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with +** the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include <metatranslator.h> + +#include <qfile.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtextstream.h> +#include <qvaluestack.h> +#include <qxml.h> + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* qmake ignore Q_OBJECT */ + +static const char MagicComment[] = "TRANSLATOR "; + +static QMap<QCString, int> needs_Q_OBJECT; +static QMap<QCString, int> lacks_Q_OBJECT; + +/* + 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, + Tok_trUtf8, Tok_translate, Tok_Q_OBJECT, Tok_Ident, + Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, + Tok_Gulbrandsen, Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen, + Tok_RightParen, Tok_Comma, Tok_Semicolon }; + +/* + The tokenizer maintains the following global variables. The names + should be self-explanatory. +*/ +static QCString yyFileName; +static int yyCh; +static char yyIdent[128]; +static size_t yyIdentLen; +static char yyComment[65536]; +static size_t yyCommentLen; +static char yyString[65536]; +static size_t yyStringLen; +static QValueStack<int> yySavedBraceDepth; +static QValueStack<int> yySavedParenDepth; +static int yyBraceDepth; +static int yyParenDepth; +static int yyLineNo; +static int yyCurLineNo; +static int yyBraceLineNo; +static int yyParenLineNo; + +// the file to read from (if reading from a file) +static FILE *yyInFile; + +// the string to read from and current position in the string (otherwise) +static QString yyInStr; +static int yyInPos; + +static int (*getChar)(); + +static int getCharFromFile() +{ + int c = getc( yyInFile ); + if ( c == '\n' ) + yyCurLineNo++; + return c; +} + +static int getCharFromString() +{ + if ( yyInPos == (int) yyInStr.length() ) { + return EOF; + } else { + return yyInStr[yyInPos++].latin1(); + } +} + +static void startTokenizer( const char *fileName, int (*getCharFunc)() ) +{ + yyInPos = 0; + getChar = getCharFunc; + + yyFileName = fileName; + yyCh = getChar(); + yySavedBraceDepth.clear(); + yySavedParenDepth.clear(); + yyBraceDepth = 0; + yyParenDepth = 0; + yyCurLineNo = 1; + yyBraceLineNo = 1; + yyParenLineNo = 1; +} + +static int getToken() +{ + const char tab[] = "abfnrtv"; + const char backTab[] = "\a\b\f\n\r\t\v"; + uint n; + + yyIdentLen = 0; + yyCommentLen = 0; + yyStringLen = 0; + + while ( yyCh != EOF ) { + yyLineNo = yyCurLineNo; + + if ( isalpha(yyCh) || yyCh == '_' ) { + do { + if ( yyIdentLen < sizeof(yyIdent) - 1 ) + yyIdent[yyIdentLen++] = (char) yyCh; + yyCh = getChar(); + } while ( isalnum(yyCh) || yyCh == '_' ); + yyIdent[yyIdentLen] = '\0'; + + switch ( yyIdent[0] ) { + case 'Q': + if ( strcmp(yyIdent + 1, "_OBJECT") == 0 ) { + return Tok_Q_OBJECT; + } else if ( strcmp(yyIdent + 1, "T_TR_NOOP") == 0 ) { + return Tok_tr; + } else if ( strcmp(yyIdent + 1, "T_TRANSLATE_NOOP") == 0 ) { + return Tok_translate; + } + break; + case 'T': + // TR() for when all else fails + if ( qstricmp(yyIdent + 1, "R") == 0 ) + return Tok_tr; + break; + case 'c': + if ( strcmp(yyIdent + 1, "lass") == 0 ) + return Tok_class; + break; + case 'f': + /* + QTranslator::findMessage() has the same parameters as + QApplication::translate(). + */ + if ( strcmp(yyIdent + 1, "indMessage") == 0 ) + return Tok_translate; + break; + case 'n': + if ( strcmp(yyIdent + 1, "amespace") == 0 ) + return Tok_namespace; + break; + case 'r': + if ( strcmp(yyIdent + 1, "eturn") == 0 ) + return Tok_return; + break; + case 's': + if ( strcmp(yyIdent + 1, "truct") == 0 ) + return Tok_class; + break; + case 't': + if ( strcmp(yyIdent + 1, "r") == 0 ) { + return Tok_tr; + } else if ( qstrcmp(yyIdent + 1, "rUtf8") == 0 ) { + return Tok_trUtf8; + } else if ( qstrcmp(yyIdent + 1, "ranslate") == 0 ) { + 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(); + } while ( yyCh != EOF && yyCh != '\n' ); + } else if ( yyCh == '*' ) { + bool metAster = FALSE; + bool metAsterSlash = FALSE; + + while ( !metAsterSlash ) { + yyCh = getChar(); + if ( yyCh == EOF ) { + fprintf( stderr, + "%s: Unterminated C++ comment starting at" + " line %d\n", + (const char *) yyFileName, yyLineNo ); + yyComment[yyCommentLen] = '\0'; + return Tok_Comment; + } + if ( yyCommentLen < sizeof(yyComment) - 1 ) + yyComment[yyCommentLen++] = (char) yyCh; + + if ( yyCh == '*' ) + metAster = TRUE; + else if ( metAster && yyCh == '/' ) + metAsterSlash = TRUE; + else + metAster = FALSE; + } + yyCh = getChar(); + yyCommentLen -= 2; + yyComment[yyCommentLen] = '\0'; + return Tok_Comment; + } + break; + case '"': + yyCh = getChar(); + + while ( yyCh != EOF && yyCh != '\n' && yyCh != '"' ) { + if ( yyCh == '\\' ) { + yyCh = getChar(); + + if ( yyCh == '\n' ) { + yyCh = getChar(); + } else if ( yyCh == 'x' ) { + QCString hex = "0"; + + yyCh = getChar(); + while ( isxdigit(yyCh) ) { + hex += (char) yyCh; + yyCh = getChar(); + } + sscanf( hex, "%x", &n ); + if ( yyStringLen < sizeof(yyString) - 1 ) + yyString[yyStringLen++] = (char) n; + } else if ( yyCh >= '0' && yyCh < '8' ) { + QCString oct = ""; + + do { + oct += (char) yyCh; + yyCh = getChar(); + } while ( yyCh >= '0' && yyCh < '8' ); + sscanf( oct, "%o", &n ); + if ( yyStringLen < sizeof(yyString) - 1 ) + yyString[yyStringLen++] = (char) n; + } else { + const char *p = strchr( tab, yyCh ); + if ( yyStringLen < sizeof(yyString) - 1 ) + yyString[yyStringLen++] = ( p == 0 ) ? + (char) yyCh : backTab[p - tab]; + yyCh = getChar(); + } + } else { + if ( yyStringLen < sizeof(yyString) - 1 ) + yyString[yyStringLen++] = (char) yyCh; + yyCh = getChar(); + } + } + yyString[yyStringLen] = '\0'; + + if ( yyCh != '"' ) + qWarning( "%s:%d: Unterminated C++ string", + (const char *) yyFileName, yyLineNo ); + + if ( yyCh == EOF ) { + return Tok_Eof; + } else { + yyCh = getChar(); + return Tok_String; + } + break; + case '-': + yyCh = getChar(); + if ( yyCh == '>' ) { + yyCh = getChar(); + return Tok_Arrow; + } + break; + case ':': + yyCh = getChar(); + if ( yyCh == ':' ) { + yyCh = getChar(); + return Tok_Gulbrandsen; + } + return Tok_Colon; + 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; + default: + yyCh = getChar(); + } + } + } + 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 int yyTok; + +static bool match( int t ) +{ + bool matches = ( yyTok == t ); + if ( matches ) + yyTok = getToken(); + return matches; +} + +static bool matchString( QCString *s ) +{ + bool matches = ( yyTok == Tok_String ); + *s = ""; + while ( yyTok == Tok_String ) { + *s += yyString; + yyTok = getToken(); + } + return matches; +} + +static bool matchEncoding( bool *utf8 ) +{ + if ( yyTok == Tok_Ident ) { + if ( strcmp(yyIdent, "QApplication") == 0 ) { + yyTok = getToken(); + if ( yyTok == Tok_Gulbrandsen ) + yyTok = getToken(); + } + *utf8 = QString( yyIdent ).endsWith( QString("UTF8") ); + yyTok = getToken(); + return TRUE; + } else { + return FALSE; + } +} + +static void parse( MetaTranslator *tor, const char *initialContext, + const char *defaultContext ) +{ + QMap<QCString, QCString> qualifiedContexts; + QStringList namespaces; + QCString context; + QCString text; + QCString com; + QCString functionContext = initialContext; + QCString prefix; + bool utf8 = FALSE; + bool missing_Q_OBJECT = FALSE; + + yyTok = getToken(); + while ( yyTok != Tok_Eof ) { + switch ( yyTok ) { + case Tok_class: + /* + Partial support for inlined functions. + */ + yyTok = getToken(); + if ( yyBraceDepth == (int) namespaces.count() && + yyParenDepth == 0 ) { + 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'. + */ + functionContext = yyIdent; + yyTok = getToken(); + } while ( yyTok == Tok_Ident ); + + while ( yyTok == Tok_Gulbrandsen ) { + yyTok = getToken(); + functionContext += "::"; + functionContext += yyIdent; + yyTok = getToken(); + } + + if ( yyTok == Tok_Colon ) { + missing_Q_OBJECT = TRUE; + } else { + functionContext = defaultContext; + } + } + break; + case Tok_namespace: + yyTok = getToken(); + if ( yyTok == Tok_Ident ) { + QCString ns = yyIdent; + yyTok = getToken(); + if ( yyTok == Tok_LeftBrace && + yyBraceDepth == (int) namespaces.count() + 1 ) + namespaces.append( QString(ns) ); + } + break; + case Tok_tr: + case Tok_trUtf8: + utf8 = ( yyTok == Tok_trUtf8 ); + yyTok = getToken(); + if ( match(Tok_LeftParen) && matchString(&text) ) { + com = ""; + if ( match(Tok_RightParen) || (match(Tok_Comma) && + matchString(&com) && match(Tok_RightParen)) ) { + if ( prefix.isNull() ) { + context = functionContext; + if ( !namespaces.isEmpty() ) + context.prepend( (namespaces.join(QString("::")) + + QString("::")).latin1() ); + } else { + context = prefix; + } + prefix = (const char *) 0; + + if ( qualifiedContexts.contains(context) ) + context = qualifiedContexts[context]; + tor->insert( MetaTranslatorMessage(context, text, com, + QString::null, utf8) ); + + if ( lacks_Q_OBJECT.contains(context) ) { + qWarning( "%s:%d: Class '%s' lacks Q_OBJECT macro", + (const char *) yyFileName, yyLineNo, + (const char *) context ); + lacks_Q_OBJECT.remove( context ); + } else { + needs_Q_OBJECT.insert( context, 0 ); + } + } + } + break; + case Tok_translate: + utf8 = FALSE; + yyTok = getToken(); + if ( match(Tok_LeftParen) && + matchString(&context) && + match(Tok_Comma) && + matchString(&text) ) { + com = ""; + if ( match(Tok_RightParen) || + (match(Tok_Comma) && + matchString(&com) && + (match(Tok_RightParen) || + match(Tok_Comma) && + matchEncoding(&utf8) && + match(Tok_RightParen))) ) + tor->insert( MetaTranslatorMessage(context, text, com, + QString::null, utf8) ); + } + break; + case Tok_Q_OBJECT: + missing_Q_OBJECT = FALSE; + yyTok = getToken(); + break; + case Tok_Ident: + if ( !prefix.isNull() ) + prefix += "::"; + prefix += yyIdent; + yyTok = getToken(); + if ( yyTok != Tok_Gulbrandsen ) + prefix = (const char *) 0; + break; + case Tok_Comment: + com = yyComment; + com = com.simplifyWhiteSpace(); + if ( com.left(sizeof(MagicComment) - 1) == MagicComment ) { + com.remove( 0, sizeof(MagicComment) - 1 ); + int k = com.find( ' ' ); + if ( k == -1 ) { + context = com; + } else { + context = com.left( k ); + com.remove( 0, k + 1 ); + tor->insert( MetaTranslatorMessage(context, "", com, + QString::null, FALSE) ); + } + + /* + Provide a backdoor for people using "using + namespace". See the manual for details. + */ + k = 0; + while ( (k = context.find("::", 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", + (const char *) yyFileName, yyLineNo ); + break; + case Tok_Gulbrandsen: + // at top level? + if ( yyBraceDepth == (int) namespaces.count() && yyParenDepth == 0 ) + functionContext = prefix; + yyTok = getToken(); + break; + case Tok_RightBrace: + case Tok_Semicolon: + if ( yyBraceDepth >= 0 && + yyBraceDepth + 1 == (int) namespaces.count() ) + namespaces.remove( namespaces.fromLast() ); + if ( yyBraceDepth == (int) namespaces.count() ) { + if ( missing_Q_OBJECT ) { + if ( needs_Q_OBJECT.contains(functionContext) ) { + qWarning( "%s:%d: Class '%s' lacks Q_OBJECT macro", + (const char *) yyFileName, yyLineNo, + (const char *) functionContext ); + } else { + lacks_Q_OBJECT.insert( functionContext, 0 ); + } + } + functionContext = defaultContext; + missing_Q_OBJECT = FALSE; + } + yyTok = getToken(); + break; + default: + yyTok = getToken(); + } + } + + if ( yyBraceDepth != 0 ) + fprintf( stderr, + "%s:%d: Unbalanced braces in C++ code (or abuse of the C++" + " preprocessor)\n", + (const char *)yyFileName, yyBraceLineNo ); + else if ( yyParenDepth != 0 ) + fprintf( stderr, + "%s:%d: Unbalanced parentheses in C++ code (or abuse of the C++" + " preprocessor)\n", + (const char *)yyFileName, yyParenLineNo ); +} + +void fetchtr_cpp( const char *fileName, MetaTranslator *tor, + const char *defaultContext, bool mustExist ) +{ + yyInFile = fopen( fileName, "r" ); + if ( yyInFile == 0 ) { + if ( mustExist ) + fprintf( stderr, + "lupdate error: Cannot open C++ source file '%s': %s\n", + fileName, strerror(errno) ); + return; + } + + startTokenizer( fileName, getCharFromFile ); + parse( tor, 0, defaultContext ); + fclose( yyInFile ); +} + +/* + In addition to C++, we support Qt Designer UI files. +*/ + +/* + Fetches tr() calls in C++ code in UI files (inside "<function>" + tag). This mechanism is obsolete. +*/ +void fetchtr_inlined_cpp( const char *fileName, const QString& in, + MetaTranslator *tor, const char *context ) +{ + yyInStr = in; + startTokenizer( fileName, getCharFromString ); + parse( tor, context, 0 ); + yyInStr = QString::null; +} + +class UiHandler : public QXmlDefaultHandler +{ +public: + UiHandler( MetaTranslator *translator, const char *fileName ) + : tor( translator ), fname( fileName ), comment( "" ) { } + + virtual bool startElement( const QString& namespaceURI, + const QString& localName, const QString& qName, + const QXmlAttributes& atts ); + virtual bool endElement( const QString& namespaceURI, + const QString& localName, const QString& qName ); + virtual bool characters( const QString& ch ); + virtual bool fatalError( const QXmlParseException& exception ); + +private: + void flush(); + + MetaTranslator *tor; + QCString fname; + QString context; + QString source; + QString comment; + + QString accum; +}; + +bool UiHandler::startElement( const QString& /* namespaceURI */, + const QString& /* localName */, + const QString& qName, + const QXmlAttributes& atts ) +{ + if ( qName == QString("item") ) { + flush(); + if ( !atts.value(QString("text")).isEmpty() ) + source = atts.value( QString("text") ); + } else if ( qName == QString("string") ) { + flush(); + } + accum.truncate( 0 ); + return TRUE; +} + +bool UiHandler::endElement( const QString& /* namespaceURI */, + const QString& /* localName */, + const QString& qName ) +{ + accum.replace( QRegExp(QString("\r\n")), "\n" ); + + if ( qName == QString("class") ) { + if ( context.isEmpty() ) + context = accum; + } else if ( qName == QString("string") ) { + source = accum; + } else if ( qName == QString("comment") ) { + comment = accum; + flush(); + } else if ( qName == QString("function") ) { + fetchtr_inlined_cpp( (const char *) fname, accum, tor, + context.latin1() ); + } else { + flush(); + } + return TRUE; +} + +bool UiHandler::characters( const QString& ch ) +{ + accum += ch; + return TRUE; +} + +bool UiHandler::fatalError( const QXmlParseException& exception ) +{ + QString msg; + msg.sprintf( "Parse error at line %d, column %d (%s).", + exception.lineNumber(), exception.columnNumber(), + exception.message().latin1() ); + fprintf( stderr, "XML error: %s\n", msg.latin1() ); + return FALSE; +} + +void UiHandler::flush() +{ + if ( !context.isEmpty() && !source.isEmpty() ) + tor->insert( MetaTranslatorMessage(context.utf8(), source.utf8(), + comment.utf8(), QString::null, + TRUE) ); + source.truncate( 0 ); + comment.truncate( 0 ); +} + +void fetchtr_ui( const char *fileName, MetaTranslator *tor, + const char * /* defaultContext */, bool mustExist ) +{ + QFile f( fileName ); + if ( !f.open(IO_ReadOnly) ) { + if ( mustExist ) + fprintf( stderr, "lupdate error: cannot open UI file '%s': %s\n", + fileName, strerror(errno) ); + return; + } + + QTextStream t( &f ); + QXmlInputSource in( t ); + QXmlSimpleReader reader; + reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE ); + reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE ); + reader.setFeature( "http://trolltech.com/xml/features/report-whitespace" + "-only-CharData", FALSE ); + QXmlDefaultHandler *hand = new UiHandler( tor, fileName ); + reader.setContentHandler( hand ); + reader.setErrorHandler( hand ); + + if ( !reader.parse(in) ) + fprintf( stderr, "%s: Parse error in UI file\n", fileName ); + reader.setContentHandler( 0 ); + reader.setErrorHandler( 0 ); + delete hand; + f.close(); +} |