/********************************************************************** ** Copyright (C) 2000 Trolltech AS. All rights reserved. ** ** This file is part of Qt Linguist. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition ** 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 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for ** information about Qt Commercial License Agreements. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /* qmake ignore Q_OBJECT */ static const char MagicComment[] = "TRANSLATOR "; static QMap needs_Q_OBJECT; static QMap 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[16384]; static size_t yyStringLen; static QValueStack yySavedBraceDepth; static QValueStack 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 'i': /* FOR KDE APPS */ if ( strcmp( yyIdent + 1, "8n") == 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 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 "" 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(); }