-rw-r--r-- | development/translation/opie-lrelease/lrelease.pro | 16 | ||||
-rw-r--r-- | development/translation/opie-lrelease/main.cpp | 150 | ||||
-rw-r--r-- | development/translation/opie-lupdate/fetchtr.cpp | 784 | ||||
-rwxr-xr-x | development/translation/opie-lupdate/lupdate | bin | 0 -> 128093 bytes | |||
-rw-r--r-- | development/translation/opie-lupdate/main.cpp | 215 | ||||
-rw-r--r-- | development/translation/opie-lupdate/merge.cpp | 115 | ||||
-rw-r--r-- | development/translation/opie-lupdate/numberh.cpp | 235 | ||||
-rw-r--r-- | development/translation/opie-lupdate/opie-lupdate.pro | 21 | ||||
-rw-r--r-- | development/translation/opie-lupdate/sametexth.cpp | 84 | ||||
-rw-r--r-- | development/translation/shared/metatranslator.cpp | 586 | ||||
-rw-r--r-- | development/translation/shared/metatranslator.h | 99 | ||||
-rw-r--r-- | development/translation/shared/opie.cpp | 40 | ||||
-rw-r--r-- | development/translation/shared/opie.h | 21 | ||||
-rw-r--r-- | development/translation/shared/proparser.cpp | 87 | ||||
-rw-r--r-- | development/translation/shared/proparser.h | 29 |
15 files changed, 2482 insertions, 0 deletions
diff --git a/development/translation/opie-lrelease/lrelease.pro b/development/translation/opie-lrelease/lrelease.pro new file mode 100644 index 0000000..fa285cb --- a/dev/null +++ b/development/translation/opie-lrelease/lrelease.pro @@ -0,0 +1,16 @@ +TEMPLATE = app +CONFIG += qt warn_on console +HEADERS = ../shared/metatranslator.h \ + ../shared/proparser.h \ + ../shared/opie.h +SOURCES = main.cpp \ + ../shared/metatranslator.cpp \ + ../shared/proparser.cpp \ + ../shared/opie.cpp + +DEFINES += QT_INTERNAL_XML + +TARGET = opie-lrelease +INCLUDEPATH += ../shared +#DESTDIR = ../../../bin + diff --git a/development/translation/opie-lrelease/main.cpp b/development/translation/opie-lrelease/main.cpp new file mode 100644 index 0000000..6008c4e --- a/dev/null +++ b/development/translation/opie-lrelease/main.cpp @@ -0,0 +1,150 @@ +/********************************************************************** +** 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <metatranslator.h> +#include <proparser.h> +#include <opie.h> + +#include <qfile.h> +#include <qregexp.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextstream.h> + +#include <errno.h> + +typedef QValueList<MetaTranslatorMessage> TML; + +static void printUsage() +{ + fprintf( stderr, "Usage:\n" + " lrelease [options] project-file\n" + " lrelease [options] ts-files\n" + "Options:\n" + " -opie OPIE dir overrides $OPIEDIR\n" + " -help Display this information and exit\n" + " -verbose\n" + " Explain what is being done\n" + " -version\n" + " Display the version of lrelease and exit\n" ); +} +static void releaseQmFile( const QString& tsFileName, bool verbose ) +{ + MetaTranslator tor; + QString qmFileName = tsFileName; + qmFileName.replace( QRegExp("\\.ts$"), "" ); + qmFileName += ".qm"; + + if ( tor.load(tsFileName) ) { + if ( verbose ) + fprintf( stderr, "Updating '%s'...\n", qmFileName.latin1() ); + if ( !tor.release(qmFileName, verbose) ) + fprintf( stderr, + "lrelease warning: For some reason, I cannot save '%s'\n", + qmFileName.latin1() ); + } else { + fprintf( stderr, + "lrelease warning: For some reason, I cannot load '%s'\n", + tsFileName.latin1() ); + } +} +static void metaQmFile( const QString &opiedir, + const QStringList& lang, + const QString& basename, + bool isLib, bool verb ) { + QString target = basename + ".ts"; + if ( isLib ) target.prepend("lib"); + + for ( QStringList::ConstIterator it = lang.begin(); it != lang.end(); + ++it ) { + QString fileName = opiedir + "/i18n/" + (*it) + "/" + target; + qWarning("Target is %s", fileName.latin1() ); + } +} +int main( int argc, char **argv ) +{ + bool verbose = FALSE; + bool metTranslations = FALSE; + int numFiles = 0; + QString opiedir; + QStringList languageList = OPIE::self()->languageList( opiedir ); + + for ( int i = 1; i < argc; i++ ) { + if ( qstrcmp(argv[i], "-help") == 0 ) { + printUsage(); + return 0; + } else if ( qstrcmp(argv[i], "-verbose") == 0 ) { + verbose = TRUE; + continue; + } else if ( qstrcmp(argv[i], "-version") == 0 ) { + fprintf( stderr, "lrelease version %s\n", QT_VERSION_STR ); + return 0; + } else if ( qstrcmp(argv[i], "-opie") == 0 ) { + if ( i+1 < argc ) { + opiedir = argv[i+1]; + languageList = OPIE::self()->languageList(opiedir); + } + } + + numFiles++; + QFile f( argv[i] ); + if ( !f.open(IO_ReadOnly) ) { + fprintf( stderr, + "lrelease error: Cannot open file '%s': %s\n", argv[i], + strerror(errno) ); + return 1; + } + + QTextStream t( &f ); + QString fullText = t.read(); + f.close(); + + if ( fullText.find(QString("<!DOCTYPE TS>")) >= 0 ) { + releaseQmFile( argv[i], verbose ); + } else { + QString target; + bool isLib = FALSE; + QMap<QString, QString> tagMap = proFileTagMap( fullText ); + QMap<QString, QString>::Iterator it; + + for ( it = tagMap.begin(); it != tagMap.end(); ++it ) { + QStringList toks = QStringList::split( ' ', it.data() ); + QStringList::Iterator t; + + for ( t = toks.begin(); t != toks.end(); ++t ) { + if ( it.key() == "TARGET" ) { + target = *t; + }else if ( it.key() == "TEMPLATE" ) { + if ( (*t).stripWhiteSpace().lower() == "lib" ) + isLib = TRUE; + } + } + } + metaQmFile( OPIE::self()->opieDir(opiedir), + languageList, target, isLib, verbose ); + } + } + + if ( numFiles == 0 ) { + printUsage(); + return 1; + } + return 0; +} diff --git a/development/translation/opie-lupdate/fetchtr.cpp b/development/translation/opie-lupdate/fetchtr.cpp new file mode 100644 index 0000000..eb25555 --- a/dev/null +++ b/development/translation/opie-lupdate/fetchtr.cpp @@ -0,0 +1,784 @@ +/********************************************************************** +** 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#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[16384]; +static size_t yyStringLen; +static QValueStack<int> yySavedBraceDepth; +static int yyBraceDepth; +static int yyParenDepth; +static int yyLineNo; +static int yyCurLineNo; + +// 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(); + yyBraceDepth = 0; + yyParenDepth = 0; + yyCurLineNo = 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 '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 ); + } + break; + case 'e': + yyCh = getChar(); + if ( yyCh == 'l' ) { + // elif, else + if ( !yySavedBraceDepth.isEmpty() ) + yyBraceDepth = yySavedBraceDepth.top(); + } else if ( yyCh == 'n' ) { + // endif + if ( !yySavedBraceDepth.isEmpty() ) + yySavedBraceDepth.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 '{': + yyBraceDepth++; + yyCh = getChar(); + return Tok_LeftBrace; + case '}': + yyBraceDepth--; + yyCh = getChar(); + return Tok_RightBrace; + case '(': + yyParenDepth++; + yyCh = getChar(); + return Tok_LeftParen; + case ')': + 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: Unbalanced braces in C++ code (or abuse of the C++" + " preprocessor)\n", + (const char *) yyFileName ); + if ( yyParenDepth != 0 ) + fprintf( stderr, + "%s: Unbalanced parentheses in C++ code (or abuse of the C++" + " preprocessor)\n", + (const char *) yyFileName ); +} + +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(); +} diff --git a/development/translation/opie-lupdate/lupdate b/development/translation/opie-lupdate/lupdate Binary files differnew file mode 100755 index 0000000..0e1604d --- a/dev/null +++ b/development/translation/opie-lupdate/lupdate diff --git a/development/translation/opie-lupdate/main.cpp b/development/translation/opie-lupdate/main.cpp new file mode 100644 index 0000000..ce65e7a --- a/dev/null +++ b/development/translation/opie-lupdate/main.cpp @@ -0,0 +1,215 @@ +/********************************************************************** +** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. +** Copyright (C) 2003 zecke +** +** 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <metatranslator.h> +#include <proparser.h> +#include <opie.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextstream.h> + +#include <errno.h> +#include <string.h> + +// defined in fetchtr.cpp +extern void fetchtr_cpp( const char *fileName, MetaTranslator *tor, + const char *defaultContext, bool mustExist ); +extern void fetchtr_ui( const char *fileName, MetaTranslator *tor, + const char *defaultContext, bool mustExist ); + +// defined in merge.cpp +extern void merge( MetaTranslator *tor, const MetaTranslator *virginTor, + bool verbose ); + +typedef QValueList<MetaTranslatorMessage> TML; + +static const char* LUPDATE_VERSION = "0.1"; + +static void printUsage() +{ + fprintf( stderr, "Usage:\n" + " opie-lupdate [options] project-file\n" + " opie-lupdate [options] source-files -ts ts-files\n" + "Options:\n" + " -opie The OPIE base dir if not supplied $OPIEDIR will be taken\n" + " -help Display this information and exit\n" + " -noobsolete\n" + " Drop all obsolete strings\n" + " -verbose\n" + " Explain what is being done\n" + " -version\n" + " Display the version of lupdate and exit\n" ); +} + +static void updateTsFiles( const MetaTranslator& fetchedTor, + const QString& opiedir, + const QStringList& languages, + const QString& basename, + const QString& codec, + bool noObsolete, bool verbose ) +{ + QStringList::ConstIterator it = languages.begin(); + for ( ; it != languages.end(); ++it ) { + QString fileName = opiedir + "/i18n/" + (*it) + "/" + basename; + MetaTranslator tor; + tor.load( fileName ); + if ( !codec.isEmpty() ) + tor.setCodec( codec ); + if ( verbose ) + fprintf( stderr, "Updating '%s'...\n", fileName.latin1() ); + merge( &tor, &fetchedTor, verbose ); + if ( noObsolete ) + tor.stripObsoleteMessages(); + tor.stripEmptyContexts(); + if ( !tor.save(fileName) ) + fprintf( stderr, "lupdate error: Cannot save '%s': %s\n", + fileName.latin1(), strerror(errno) ); + } +} + +int main( int argc, char **argv ) +{ + QString defaultContext = "@default"; + MetaTranslator fetchedTor; + QCString codec; + QStringList tsFileNames; + QString opiedir; + QString translationBase; + QString target; + + bool verbose = FALSE; + bool noObsolete = FALSE; + bool metSomething = FALSE; + bool isLib = FALSE; + int numFiles = 0; + + int i; + + QStringList languageList = OPIE::self()->languageList(opiedir); + + for ( i = 1; i < argc; i++ ) { + if ( qstrcmp(argv[i], "-help") == 0 ) { + printUsage(); + return 0; + } else if ( qstrcmp(argv[i], "-noobsolete") == 0 ) { + noObsolete = TRUE; + continue; + } else if ( qstrcmp(argv[i], "-verbose") == 0 ) { + verbose = TRUE; + continue; + } else if ( qstrcmp(argv[i], "-version") == 0 ) { + fprintf( stderr, "lupdate version %s\n", LUPDATE_VERSION ); + return 0; + } else if ( qstrcmp(argv[i], "-opie") == 0 ) { + if( i+1 < argc ) { + opiedir = argv[i+1]; + languageList = OPIE::self()->languageList(opiedir); + } + i++; // UGLY but we want to skip the next argument + continue; + } + + numFiles++; + + QString fullText; + + QFile f( argv[i] ); + if ( !f.open(IO_ReadOnly) ) { + fprintf( stderr, "lupdate error: Cannot open file '%s': %s\n", + argv[i], strerror(errno) ); + return 1; + } + + QTextStream t( &f ); + fullText = t.read(); + f.close(); + + fetchedTor = MetaTranslator(); + codec.truncate( 0 ); + tsFileNames.clear(); + isLib = FALSE; + + QMap<QString, QString> tagMap = proFileTagMap( fullText ); + QMap<QString, QString>::Iterator it; + + for ( it = tagMap.begin(); it != tagMap.end(); ++it ) { + QStringList toks = QStringList::split( ' ', it.data() ); + QStringList::Iterator t; + + for ( t = toks.begin(); t != toks.end(); ++t ) { + if ( it.key() == "HEADERS" || it.key() == "SOURCES" ) { + fetchtr_cpp( *t, &fetchedTor, defaultContext, TRUE ); + metSomething = TRUE; + } else if ( it.key() == "INTERFACES" || + it.key() == "FORMS" ) { + fetchtr_ui( *t, &fetchedTor, defaultContext, TRUE ); + fetchtr_cpp( *t + ".h", &fetchedTor, defaultContext, + FALSE ); + metSomething = TRUE; + } else if ( it.key() == "TRANSLATIONS" ) { + // we do not care for that attribute anymore + //tsFileNames.append( *t ); + metSomething = TRUE; + } else if ( it.key() == "CODEC" ) { + codec = (*t).latin1(); + } else if ( it.key() == "TARGET" ) { + target = *t; + metSomething = TRUE; + } else if ( it.key() == "TEMPLATE" ) { + if ( (*t).stripWhiteSpace().lower() == "lib" ) + isLib = true; + } + } + } + /** + * We know the $OPIEDIR or have opiedir + * we've a list of languages (de,en,gb,foo,bar) + * we've got the TARGET and we no it's the lib + * so let's do that + * $OPIEDIR/language[i]/ifLibAppendLib$TARGET.ts + */ + qWarning("TARGET %s IsLib:%d", target.latin1(), isLib ); + qWarning("LANGS %s", languageList.join(";").latin1() ); + qWarning("OPIEDIR %s", OPIE::self()->opieDir(opiedir).latin1() ); + if (isLib ) + target.prepend("lib"); + target += ".ts"; + updateTsFiles( fetchedTor, OPIE::self()->opieDir(opiedir), + languageList, target, codec, noObsolete, verbose ); + + if ( !metSomething ) { + fprintf( stderr, + "lupdate warning: File '%s' does not look like a" + " project file\n", + argv[i] ); + } + + } + + if ( numFiles == 0 ) { + printUsage(); + return 1; + } + return 0; +} diff --git a/development/translation/opie-lupdate/merge.cpp b/development/translation/opie-lupdate/merge.cpp new file mode 100644 index 0000000..a96104e --- a/dev/null +++ b/development/translation/opie-lupdate/merge.cpp @@ -0,0 +1,115 @@ +/********************************************************************** +** 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <metatranslator.h> + +// defined in numberh.cpp +extern void applyNumberHeuristic( MetaTranslator *tor, bool verbose ); +// defined in sametexth.cpp +extern void applySameTextHeuristic( MetaTranslator *tor, bool verbose ); + +typedef QValueList<MetaTranslatorMessage> TML; + +/* + Merges two MetaTranslator objects into the first one. 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. +*/ + +void merge( MetaTranslator *tor, const MetaTranslator *virginTor, bool verbose ) +{ + int known = 0; + int neww = 0; + int obsoleted = 0; + TML all = tor->messages(); + TML::Iterator it; + + /* + The types of all the messages from the vernacular translator + are updated according to the virgin translator. + */ + for ( it = all.begin(); it != all.end(); ++it ) { + MetaTranslatorMessage::Type newType; + MetaTranslatorMessage m = *it; + + // skip context comment + if ( !QCString((*it).sourceText()).isEmpty() ) { + if ( !virginTor->contains((*it).context(), (*it).sourceText(), + (*it).comment()) ) { + newType = MetaTranslatorMessage::Obsolete; + if ( m.type() != MetaTranslatorMessage::Obsolete ) + obsoleted++; + } else { + switch ( m.type() ) { + case MetaTranslatorMessage::Finished: + newType = MetaTranslatorMessage::Finished; + known++; + break; + case MetaTranslatorMessage::Unfinished: + newType = MetaTranslatorMessage::Unfinished; + known++; + break; + case MetaTranslatorMessage::Obsolete: + newType = MetaTranslatorMessage::Unfinished; + neww++; + } + } + + if ( newType != m.type() ) { + m.setType( newType ); + tor->insert( m ); + } + } + } + + /* + Messages found only in the virgin translator are added to the + vernacular translator. Among these are all the context comments. + */ + all = virginTor->messages(); + + for ( it = all.begin(); it != all.end(); ++it ) { + if ( !tor->contains((*it).context(), (*it).sourceText(), + (*it).comment()) ) { + tor->insert( *it ); + if ( !QCString((*it).sourceText()).isEmpty() ) + neww++; + } + } + + /* + The same-text heuristic handles cases where a message has an + obsolete counterpart with a different context or comment. + */ + applySameTextHeuristic( tor, verbose ); + + /* + The number heuristic handles cases where a message has an + obsolete counterpart with mostly numbers differing in the + source text. + */ + applyNumberHeuristic( tor, verbose ); + + if ( verbose ) + fprintf( stderr, " %d known, %d new and %d obsoleted messages\n", known, + neww, obsoleted ); +} diff --git a/development/translation/opie-lupdate/numberh.cpp b/development/translation/opie-lupdate/numberh.cpp new file mode 100644 index 0000000..f7b7bf8 --- a/dev/null +++ b/development/translation/opie-lupdate/numberh.cpp @@ -0,0 +1,235 @@ +/********************************************************************** +** Copyright (C) 2000-2002 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <metatranslator.h> + +#include <qmemarray.h> +#include <qcstring.h> +#include <qmap.h> +#include <qstringlist.h> + +#include <ctype.h> + +typedef QMap<QCString, MetaTranslatorMessage> TMM; +typedef QValueList<MetaTranslatorMessage> TML; + +static bool isDigitFriendly( int c ) +{ + return ispunct( c ) || isspace( c ); +} + +static int numberLength( const char *s ) +{ + int i = 0; + + if ( isdigit(s[0]) ) { + do { + i++; + } while ( isdigit(s[i]) || + (isDigitFriendly(s[i]) && + (isdigit(s[i + 1]) || + (isDigitFriendly(s[i + 1]) && isdigit(s[i + 2])))) ); + } + return i; +} + +/* + Returns a version of 'key' where all numbers have been replaced by zeroes. If + there were none, returns "". +*/ +static QCString zeroKey( const char *key ) +{ + QCString zeroed( strlen(key) + 1 ); + char *z = zeroed.data(); + int i = 0, j = 0; + int len; + bool metSomething = FALSE; + + while ( key[i] != '\0' ) { + len = numberLength( key + i ); + if ( len > 0 ) { + i += len; + z[j++] = '0'; + metSomething = TRUE; + } else { + z[j++] = key[i++]; + } + } + z[j] = '\0'; + + if ( metSomething ) + return zeroed; + else + return ""; +} + +static QString translationAttempt( const QString& oldTranslation, + const char *oldSource, + const char *newSource ) +{ + int p = zeroKey( oldSource ).contains( '0' ); + int oldSourceLen = qstrlen( oldSource ); + QString attempt; + QStringList oldNumbers; + QStringList newNumbers; + QMemArray<bool> met( p ); + QMemArray<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 < oldSourceLen; i++, j++ ) { + m = numberLength( oldSource + i ); + n = numberLength( newSource + j ); + if ( m > 0 ) { + oldNumbers.append( QCString(oldSource + i, m + 1) ); + newNumbers.append( QCString(newSource + 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 < (int) 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] == (int) oldNumbers[k].length() && + numberLength(oldTranslation.latin1() + (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( " {" ) + newNumbers[k] + QString( "?}" ); + } + + /* + 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( " {" ) + newNumbers[k] + QString( " or " ) + + newNumbers[ell] + QString( "?}" ); + } + } + return attempt; +} + +/* + Augments a MetaTranslator 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. +*/ +void applyNumberHeuristic( MetaTranslator *tor, bool verbose ) +{ + 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 ) { + if ( (*it).type() == MetaTranslatorMessage::Unfinished ) { + if ( (*it).translation().isEmpty() ) + untranslated.insert( zeroKey((*it).sourceText()), *it ); + } else if ( !(*it).translation().isEmpty() ) { + translated.insert( zeroKey((*it).sourceText()), *it ); + } + } + + for ( u = untranslated.begin(); u != untranslated.end(); ++u ) { + t = translated.find( u.key() ); + if ( t != translated.end() && !t.key().isEmpty() && + qstrcmp((*t).sourceText(), (*u).sourceText()) != 0 ) { + MetaTranslatorMessage m( *u ); + m.setTranslation( translationAttempt((*t).translation(), + (*t).sourceText(), + (*u).sourceText()) ); + tor->insert( m ); + inserted++; + } + } + if ( verbose && inserted != 0 ) + fprintf( stderr, " number heuristic provided %d translation%s\n", + inserted, inserted == 1 ? "" : "s" ); +} diff --git a/development/translation/opie-lupdate/opie-lupdate.pro b/development/translation/opie-lupdate/opie-lupdate.pro new file mode 100644 index 0000000..ea51c8b --- a/dev/null +++ b/development/translation/opie-lupdate/opie-lupdate.pro @@ -0,0 +1,21 @@ +TEMPLATE = app +CONFIG += qt warn_on console +HEADERS = ../shared/metatranslator.h \ + ../shared/proparser.h \ + ../shared/opie.h + +SOURCES = fetchtr.cpp \ + main.cpp \ + merge.cpp \ + numberh.cpp \ + sametexth.cpp \ + ../shared/metatranslator.cpp \ + ../shared/proparser.cpp \ + ../shared/opie.cpp + +DEFINES += QT_INTERNAL_XML + +TARGET = opie-lupdate +INCLUDEPATH += ../shared +#DESTDIR = + diff --git a/development/translation/opie-lupdate/sametexth.cpp b/development/translation/opie-lupdate/sametexth.cpp new file mode 100644 index 0000000..574cfd5 --- a/dev/null +++ b/development/translation/opie-lupdate/sametexth.cpp @@ -0,0 +1,84 @@ +/********************************************************************** +** Copyright (C) 2000-2002 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <metatranslator.h> + +#include <qcstring.h> +#include <qmap.h> + +typedef QMap<QCString, MetaTranslatorMessage> TMM; +typedef QValueList<MetaTranslatorMessage> TML; + +/* + Augments a MetaTranslator 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. +*/ + +void applySameTextHeuristic( MetaTranslator *tor, bool verbose ) +{ + 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).type() == MetaTranslatorMessage::Unfinished ) { + if ( (*it).translation().isEmpty() ) + untranslated.append( *it ); + } else { + QCString 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).translation() != (*it).translation() ) { + translated.remove( key ); + avoid.insert( key, *it ); + } + } else if ( !avoid.contains(key) && + !(*it).translation().isEmpty() ) { + translated.insert( key, *it ); + } + } + } + + for ( u = untranslated.begin(); u != untranslated.end(); ++u ) { + QCString key = (*u).sourceText(); + t = translated.find( key ); + if ( t != translated.end() ) { + MetaTranslatorMessage m( *u ); + m.setTranslation( (*t).translation() ); + tor->insert( m ); + inserted++; + } + } + if ( verbose && inserted != 0 ) + fprintf( stderr, " same-text heuristic provided %d translation%s\n", + inserted, inserted == 1 ? "" : "s" ); +} diff --git a/development/translation/shared/metatranslator.cpp b/development/translation/shared/metatranslator.cpp new file mode 100644 index 0000000..a01e1eb --- a/dev/null +++ b/development/translation/shared/metatranslator.cpp @@ -0,0 +1,586 @@ +/********************************************************************** +** Copyright (C) 2000-2002 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "metatranslator.h" + +#include <qapplication.h> +#include <qcstring.h> +#include <qfile.h> +#include <qmessagebox.h> +#include <qtextcodec.h> +#include <qtextstream.h> +#include <qxml.h> + +static bool encodingIsUtf8( const QXmlAttributes& atts ) +{ + for ( int i = 0; i < atts.length(); i++ ) { + // utf8="true" is a pre-3.0 syntax + if ( atts.qName(i) == QString("utf8") ) { + return ( atts.value(i) == QString("true") ); + } else if ( atts.qName(i) == QString("encoding") ) { + return ( atts.value(i) == QString("UTF-8") ); + } + } + return FALSE; +} + +class TsHandler : public QXmlDefaultHandler +{ +public: + TsHandler( MetaTranslator *translator ) + : tor( translator ), type( MetaTranslatorMessage::Finished ), + inMessage( FALSE ), ferrorCount( 0 ), contextIsUtf8( FALSE ), + messageIsUtf8( FALSE ) { } + + 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: + MetaTranslator *tor; + MetaTranslatorMessage::Type type; + bool inMessage; + QString context; + QString source; + QString comment; + QString translation; + + QString accum; + int ferrorCount; + bool contextIsUtf8; + bool messageIsUtf8; +}; + +bool TsHandler::startElement( const QString& /* namespaceURI */, + const QString& /* localName */, + const QString& qName, + const QXmlAttributes& atts ) +{ + if ( qName == QString("byte") ) { + for ( int i = 0; i < atts.length(); i++ ) { + if ( atts.qName(i) == QString("value") ) { + QString value = atts.value( i ); + int base = 10; + if ( value.startsWith("x") ) { + base = 16; + value = value.mid( 1 ); + } + int n = value.toUInt( 0, base ); + if ( n != 0 ) + accum += QChar( n ); + } + } + } else { + if ( qName == QString("context") ) { + context.truncate( 0 ); + source.truncate( 0 ); + comment.truncate( 0 ); + translation.truncate( 0 ); + contextIsUtf8 = encodingIsUtf8( atts ); + } else if ( qName == QString("message") ) { + inMessage = TRUE; + type = MetaTranslatorMessage::Finished; + source.truncate( 0 ); + comment.truncate( 0 ); + translation.truncate( 0 ); + messageIsUtf8 = encodingIsUtf8( atts ); + } else if ( qName == QString("translation") ) { + for ( int i = 0; i < atts.length(); i++ ) { + if ( atts.qName(i) == QString("type") ) { + if ( atts.value(i) == QString("unfinished") ) + type = MetaTranslatorMessage::Unfinished; + else if ( atts.value(i) == QString("obsolete") ) + type = MetaTranslatorMessage::Obsolete; + else + type = MetaTranslatorMessage::Finished; + } + } + } + accum.truncate( 0 ); + } + return TRUE; +} + +bool TsHandler::endElement( const QString& /* namespaceURI */, + const QString& /* localName */, + const QString& qName ) +{ + if ( qName == QString("codec") || qName == QString("defaultcodec") ) { + // "codec" is a pre-3.0 syntax + tor->setCodec( accum ); + } else if ( qName == QString("name") ) { + context = accum; + } else if ( qName == QString("source") ) { + source = accum; + } else if ( qName == QString("comment") ) { + if ( inMessage ) { + comment = accum; + } else { + if ( contextIsUtf8 ) + tor->insert( MetaTranslatorMessage(context.utf8(), "", + accum.utf8(), QString::null, TRUE, + MetaTranslatorMessage::Unfinished) ); + else + tor->insert( MetaTranslatorMessage(context.ascii(), "", + accum.ascii(), QString::null, FALSE, + MetaTranslatorMessage::Unfinished) ); + } + } else if ( qName == QString("translation") ) { + translation = accum; + } else if ( qName == QString("message") ) { + if ( messageIsUtf8 ) + tor->insert( MetaTranslatorMessage(context.utf8(), source.utf8(), + comment.utf8(), translation, + TRUE, type) ); + else + tor->insert( MetaTranslatorMessage(context.ascii(), source.ascii(), + comment.ascii(), translation, + FALSE, type) ); + inMessage = FALSE; + } + return TRUE; +} + +bool TsHandler::characters( const QString& ch ) +{ + QString t = ch; + t.replace( "\r", "" ); + accum += t; + return TRUE; +} + +bool TsHandler::fatalError( const QXmlParseException& exception ) +{ + if ( ferrorCount++ == 0 ) { + QString msg; + msg.sprintf( "Parse error at line %d, column %d (%s).", + exception.lineNumber(), exception.columnNumber(), + exception.message().latin1() ); + if ( qApp == 0 ) + fprintf( stderr, "XML error: %s\n", msg.latin1() ); + else + QMessageBox::information( qApp->mainWidget(), + QObject::tr("Qt Linguist"), msg ); + } + return FALSE; +} + +static QString numericEntity( int ch ) +{ + return QString( ch <= 0x20 ? "<byte value=\"x%1\"/>" : "&#x%1;" ) + .arg( ch, 0, 16 ); +} + +static QString protect( const QCString& str ) +{ + QString result; + int len = (int) str.length(); + for ( int k = 0; k < len; k++ ) { + switch( str[k] ) { + case '\"': + result += QString( """ ); + break; + case '&': + result += QString( "&" ); + break; + case '>': + result += QString( ">" ); + break; + case '<': + result += QString( "<" ); + break; + case '\'': + result += QString( "'" ); + break; + default: + if ( (uchar) str[k] < 0x20 && str[k] != '\n' ) + result += numericEntity( (uchar) str[k] ); + else + result += str[k]; + } + } + return result; +} + +static QString evilBytes( const QCString& str, bool utf8 ) +{ + if ( utf8 ) { + return protect( str ); + } else { + QString result; + QCString t = protect( str ).latin1(); + int len = (int) t.length(); + for ( int k = 0; k < len; k++ ) { + if ( (uchar) t[k] >= 0x7f ) + result += numericEntity( (uchar) t[k] ); + else + result += QChar( t[k] ); + } + return result; + } +} + +MetaTranslatorMessage::MetaTranslatorMessage() + : utfeight( FALSE ), ty( Unfinished ) +{ +} + +MetaTranslatorMessage::MetaTranslatorMessage( const char *context, + const char *sourceText, + const char *comment, + const QString& translation, + bool utf8, Type type ) + : QTranslatorMessage( context, sourceText, comment, translation ), + utfeight( FALSE ), ty( type ) +{ + /* + Don't use UTF-8 if it makes no difference. UTF-8 should be + reserved for the real problematic case: non-ASCII (possibly + non-Latin-1) characters in .ui files. + */ + if ( utf8 ) { + if ( sourceText != 0 ) { + int i = 0; + while ( sourceText[i] != '\0' ) { + if ( (uchar) sourceText[i] >= 0x80 ) { + utfeight = TRUE; + break; + } + i++; + } + } + if ( !utfeight && comment != 0 ) { + int i = 0; + while ( comment[i] != '\0' ) { + if ( (uchar) comment[i] >= 0x80 ) { + utfeight = TRUE; + break; + } + i++; + } + } + } +} + +MetaTranslatorMessage::MetaTranslatorMessage( const MetaTranslatorMessage& m ) + : QTranslatorMessage( m ), utfeight( m.utfeight ), ty( m.ty ) +{ +} + +MetaTranslatorMessage& MetaTranslatorMessage::operator=( + const MetaTranslatorMessage& m ) +{ + QTranslatorMessage::operator=( m ); + utfeight = m.utfeight; + ty = m.ty; + return *this; +} + +bool MetaTranslatorMessage::operator==( const MetaTranslatorMessage& m ) const +{ + return qstrcmp( context(), m.context() ) == 0 && + qstrcmp( sourceText(), m.sourceText() ) == 0 && + qstrcmp( comment(), m.comment() ) == 0; +} + +bool MetaTranslatorMessage::operator<( const MetaTranslatorMessage& m ) const +{ + int delta = qstrcmp( context(), m.context() ); + if ( delta == 0 ) + delta = qstrcmp( sourceText(), m.sourceText() ); + if ( delta == 0 ) + delta = qstrcmp( comment(), m.comment() ); + return delta < 0; +} + +MetaTranslator::MetaTranslator() + : codecName( "ISO-8859-1" ), codec( 0 ) +{ +} + +MetaTranslator::MetaTranslator( const MetaTranslator& tor ) + : mm( tor.mm ), codecName( tor.codecName ), codec( tor.codec ) +{ + +} + +MetaTranslator& MetaTranslator::operator=( const MetaTranslator& tor ) +{ + mm = tor.mm; + codecName = tor.codecName; + codec = tor.codec; + return *this; +} + +bool MetaTranslator::load( const QString& filename ) +{ + mm.clear(); + + QFile f( filename ); + if ( !f.open(IO_ReadOnly) ) + return FALSE; + + QTextStream t( &f ); + QXmlInputSource in( t ); + QXmlSimpleReader reader; + // don't click on these! + 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 TsHandler( this ); + reader.setContentHandler( hand ); + reader.setErrorHandler( hand ); + + bool ok = reader.parse( in ); + reader.setContentHandler( 0 ); + reader.setErrorHandler( 0 ); + delete hand; + f.close(); + if ( !ok ) + mm.clear(); + return ok; +} + +bool MetaTranslator::save( const QString& filename ) const +{ + QFile f( filename ); + if ( !f.open(IO_WriteOnly) ) + return FALSE; + + QTextStream t( &f ); + t.setCodec( QTextCodec::codecForName("ISO-8859-1") ); + + t << "<!DOCTYPE TS><TS>\n"; + if ( codecName != "ISO-8859-1" ) + t << "<defaultcodec>" << codecName << "</defaultcodec>\n"; + TMM::ConstIterator m = mm.begin(); + while ( m != mm.end() ) { + TMMInv inv; + TMMInv::Iterator i; + bool contextIsUtf8 = m.key().utf8(); + QCString context = m.key().context(); + QCString comment = ""; + + do { + if ( QCString(m.key().sourceText()).isEmpty() ) { + if ( m.key().type() != MetaTranslatorMessage::Obsolete ) { + contextIsUtf8 = m.key().utf8(); + comment = QCString( m.key().comment() ); + } + } else { + inv.insert( *m, m.key() ); + } + } while ( ++m != mm.end() && QCString(m.key().context()) == context ); + + t << "<context"; + if ( contextIsUtf8 ) + t << " encoding=\"UTF-8\""; + t << ">\n"; + t << " <name>" << evilBytes( context, contextIsUtf8 ) + << "</name>\n"; + if ( !comment.isEmpty() ) + t << " <comment>" << evilBytes( comment, contextIsUtf8 ) + << "</comment>\n"; + + for ( i = inv.begin(); i != inv.end(); ++i ) { + // no need for such noise + if ( (*i).type() == MetaTranslatorMessage::Obsolete && + (*i).translation().isEmpty() ) + continue; + + t << " <message"; + if ( (*i).utf8() ) + t << " encoding=\"UTF-8\""; + t << ">\n" + << " <source>" << evilBytes( (*i).sourceText(), + (*i).utf8() ) + << "</source>\n"; + if ( !QCString((*i).comment()).isEmpty() ) + t << " <comment>" << evilBytes( (*i).comment(), + (*i).utf8() ) + << "</comment>\n"; + t << " <translation"; + if ( (*i).type() == MetaTranslatorMessage::Unfinished ) + t << " type=\"unfinished\""; + else if ( (*i).type() == MetaTranslatorMessage::Obsolete ) + t << " type=\"obsolete\""; + t << ">" << protect( (*i).translation().utf8() ) + << "</translation>\n"; + t << " </message>\n"; + } + t << "</context>\n"; + } + t << "</TS>\n"; + f.close(); + return TRUE; +} + +bool MetaTranslator::release( const QString& filename, bool verbose ) const +{ + QTranslator tor( 0 ); + int finished = 0; + int unfinished = 0; + int untranslated = 0; + TMM::ConstIterator m; + + for ( m = mm.begin(); m != mm.end(); ++m ) { + if ( m.key().type() != MetaTranslatorMessage::Obsolete ) { + if ( m.key().translation().isEmpty() ) { + untranslated++; + } else { + if ( m.key().type() == MetaTranslatorMessage::Unfinished ) + unfinished++; + else + finished++; + + QCString context = m.key().context(); + QCString sourceText = m.key().sourceText(); + QCString comment = m.key().comment(); + QString translation = m.key().translation(); + + /* + Drop the comment in (context, sourceText, comment), + unless (context, sourceText, "") already exists, or + unless we already dropped the comment of (context, + sourceText, comment0). + */ + if ( comment.isEmpty() + || contains(context, sourceText, "") + || !tor.findMessage(context, sourceText, "").translation() + .isNull() ) { + tor.insert( m.key() ); + } else { + tor.insert( QTranslatorMessage(context, sourceText, "", + translation) ); + } + } + } + } + + bool saved = tor.save( filename, QTranslator::Stripped ); + if ( saved && verbose ) + fprintf( stderr, + " %d finished, %d unfinished and %d untranslated messages\n", + finished, unfinished, untranslated ); + + return saved; +} + +bool MetaTranslator::contains( const char *context, const char *sourceText, + const char *comment ) const +{ + return mm.find( MetaTranslatorMessage(context, sourceText, comment) ) != + mm.end(); +} + +void MetaTranslator::insert( const MetaTranslatorMessage& m ) +{ + int pos = mm.count(); + TMM::Iterator n = mm.find( m ); + if ( n != mm.end() ) + pos = *n; + mm.replace( m, pos ); +} + +void MetaTranslator::stripObsoleteMessages() +{ + TMM newmm; + + TMM::Iterator m = mm.begin(); + while ( m != mm.end() ) { + if ( m.key().type() != MetaTranslatorMessage::Obsolete ) + newmm.insert( m.key(), *m ); + ++m; + } + mm = newmm; +} + +void MetaTranslator::stripEmptyContexts() +{ + TMM newmm; + + TMM::Iterator m = mm.begin(); + while ( m != mm.end() ) { + if ( QCString(m.key().sourceText()).isEmpty() ) { + TMM::Iterator n = m; + ++n; + // the context comment is followed by other messages + if ( n != newmm.end() && + qstrcmp(m.key().context(), n.key().context()) == 0 ) + newmm.insert( m.key(), *m ); + } else { + newmm.insert( m.key(), *m ); + } + ++m; + } + mm = newmm; +} + +void MetaTranslator::setCodec( const char *name ) +{ + const int latin1 = 4; + + codecName = name; + codec = QTextCodec::codecForName( name ); + if ( codec == 0 || codec->mibEnum() == latin1 ) + codec = 0; +} + +QString MetaTranslator::toUnicode( const char *str, bool utf8 ) const +{ + if ( utf8 ) + return QString::fromUtf8( str ); + else if ( codec == 0 ) + return QString( str ); + else + return codec->toUnicode( str ); +} + +QValueList<MetaTranslatorMessage> MetaTranslator::messages() const +{ + int n = mm.count(); + TMM::ConstIterator *t = new TMM::ConstIterator[n + 1]; + TMM::ConstIterator m; + for ( m = mm.begin(); m != mm.end(); ++m ) + t[*m] = m; + + QValueList<MetaTranslatorMessage> val; + for ( int i = 0; i < n; i++ ) + val.append( t[i].key() ); + + delete[] t; + return val; +} + +QValueList<MetaTranslatorMessage> MetaTranslator::translatedMessages() const +{ + QValueList<MetaTranslatorMessage> val; + TMM::ConstIterator m; + for ( m = mm.begin(); m != mm.end(); ++m ) { + if ( m.key().type() == MetaTranslatorMessage::Finished ) + val.append( m.key() ); + } + return val; +} diff --git a/development/translation/shared/metatranslator.h b/development/translation/shared/metatranslator.h new file mode 100644 index 0000000..d35b202 --- a/dev/null +++ b/development/translation/shared/metatranslator.h @@ -0,0 +1,99 @@ +/********************************************************************** +** Copyright (C) 2000-2002 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef METATRANSLATOR_H +#define METATRANSLATOR_H + +#include <qmap.h> +#include <qstring.h> +#include <qtranslator.h> +#include <qvaluelist.h> + +class QTextCodec; + +class MetaTranslatorMessage : public QTranslatorMessage +{ +public: + enum Type { Unfinished, Finished, Obsolete }; + + MetaTranslatorMessage(); + MetaTranslatorMessage( const char *context, const char *sourceText, + const char *comment, + const QString& translation = QString::null, + bool utf8 = FALSE, Type type = Unfinished ); + MetaTranslatorMessage( const MetaTranslatorMessage& m ); + + MetaTranslatorMessage& operator=( const MetaTranslatorMessage& m ); + + void setType( Type nt ) { ty = nt; } + Type type() const { return ty; } + bool utf8() const { return utfeight; } + + bool operator==( const MetaTranslatorMessage& m ) const; + bool operator!=( const MetaTranslatorMessage& m ) const + { return !operator==( m ); } + bool operator<( const MetaTranslatorMessage& m ) const; + bool operator<=( const MetaTranslatorMessage& m ) + { return !operator>( m ); } + bool operator>( const MetaTranslatorMessage& m ) const + { return this->operator<( m ); } + bool operator>=( const MetaTranslatorMessage& m ) const + { return !operator<( m ); } + +private: + bool utfeight; + Type ty; +}; + +class MetaTranslator +{ +public: + MetaTranslator(); + MetaTranslator( const MetaTranslator& tor ); + + MetaTranslator& operator=( const MetaTranslator& tor ); + + bool load( const QString& filename ); + bool save( const QString& filename ) const; + bool release( const QString& filename, bool verbose = FALSE ) const; + + bool contains( const char *context, const char *sourceText, + const char *comment ) const; + void insert( const MetaTranslatorMessage& m ); + + void stripObsoleteMessages(); + void stripEmptyContexts(); + + void setCodec( const char *name ); + QString toUnicode( const char *str, bool utf8 ) const; + + QValueList<MetaTranslatorMessage> messages() const; + QValueList<MetaTranslatorMessage> translatedMessages() const; + +private: + typedef QMap<MetaTranslatorMessage, int> TMM; + typedef QMap<int, MetaTranslatorMessage> TMMInv; + + TMM mm; + QCString codecName; + QTextCodec *codec; +}; + +#endif diff --git a/development/translation/shared/opie.cpp b/development/translation/shared/opie.cpp new file mode 100644 index 0000000..c5c72d1 --- a/dev/null +++ b/development/translation/shared/opie.cpp @@ -0,0 +1,40 @@ +#include <stdlib.h> + +#include <qdir.h> + +#include "opie.h" + +OPIE* OPIE::m_self = 0; + + +OPIE::OPIE() { +} +OPIE::~OPIE() { +} +OPIE* OPIE::self() { + if (!m_self ) m_self = new OPIE; + return m_self; +} +QStringList OPIE::languageList( const QString& _opieDir )const { + QString opieDi = opieDir( _opieDir ); + + QStringList langs; + QDir dir( opieDi + "/i18n/"); + if (!dir.exists() ) return langs; + langs = dir.entryList( QDir::Dirs ); + + langs.remove("CVS"); // hey this no language + langs.remove("unmaintained"); // remove this one too + langs.remove("."); + langs.remove(".."); + + + + return langs; +} +QString OPIE::opieDir( const QString& _opieDir ) const{ + if (!_opieDir.isEmpty() ) return _opieDir; + char* dir = ::getenv("OPIEDIR"); + if (!dir ) return QString::null; + return QString::fromLatin1(dir); +} diff --git a/development/translation/shared/opie.h b/development/translation/shared/opie.h new file mode 100644 index 0000000..4646bb0 --- a/dev/null +++ b/development/translation/shared/opie.h @@ -0,0 +1,21 @@ +#ifndef OPIE_H +#define OPIE_H + +#include <qstring.h> +#include <qstringlist.h> + +class OPIE { +public: + static OPIE* self(); + /** get the list of languages */ + QStringList languageList(const QString& opiedir = QString::null)const; + QString opieDir(const QString& opieDir)const; + +private: + OPIE(); + ~OPIE(); + static OPIE* m_self; + +}; + +#endif diff --git a/development/translation/shared/proparser.cpp b/development/translation/shared/proparser.cpp new file mode 100644 index 0000000..21d2f86 --- a/dev/null +++ b/development/translation/shared/proparser.cpp @@ -0,0 +1,87 @@ +/********************************************************************** +** Copyright (C) 2000-2002 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "proparser.h" + +#include <qregexp.h> +#include <qstringlist.h> + +QMap<QString, QString> proFileTagMap( const QString& text ) +{ + QString t = text; + + /* + Strip comments, merge lines ending with backslash, add + spaces around '=' and '+=', replace '\n' with ';', and + simplify white spaces. + */ + t.replace( QRegExp(QString("#[^\n]$")), QString(" ") ); + t.replace( QRegExp(QString("\\\\\\s*\n")), QString(" ") ); + t.replace( "=", QString(" = ") ); + t.replace( "+ =", QString(" += ") ); + t.replace( "\n", QString(";") ); + t = t.simplifyWhiteSpace(); + + QMap<QString, QString> tagMap; + + QStringList lines = QStringList::split( QChar(';'), t ); + QStringList::Iterator line; + for ( line = lines.begin(); line != lines.end(); ++line ) { + QStringList toks = QStringList::split( QChar(' '), *line ); + + if ( toks.count() >= 3 && + (toks[1] == QString("=") || toks[1] == QString("+=")) ) { + QString tag = toks.first(); + int k = tag.findRev( QChar(':') ); // as in 'unix:' + if ( k != -1 ) + tag = tag.mid( k + 1 ); + toks.remove( toks.begin() ); + + QString action = toks.first(); + toks.remove( toks.begin() ); + + if ( tagMap.contains(tag) ) { + if ( action == QString("=") ) + tagMap.replace( tag, toks.join(QChar(' ')) ); + else + tagMap[tag] += QChar( ' ' ) + toks.join( QChar(' ') ); + } else { + tagMap[tag] = toks.join( QChar(' ') ); + } + } + } + + QRegExp var( "\\$\\$[a-zA-Z0-9_]+" ); + QMap<QString, QString>::Iterator it; + for ( it = tagMap.begin(); it != tagMap.end(); ++it ) { + int i = 0; + + while ( (i = var.search(it.data(), i)) != -1 ) { + int len = var.matchedLength(); + QString invocation = (*it).mid( i + 2, len - 2 ); + QString after; + if ( tagMap.contains(invocation) ) + after = tagMap[invocation]; + (*it).replace( i, len, after ); + i += after.length(); + } + } + return tagMap; +} diff --git a/development/translation/shared/proparser.h b/development/translation/shared/proparser.h new file mode 100644 index 0000000..6a61d90 --- a/dev/null +++ b/development/translation/shared/proparser.h @@ -0,0 +1,29 @@ +/********************************************************************** +** 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. +** +** 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. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef PROPARSER_H +#define PROPARSER_H + +#include <qmap.h> +#include <qstring.h> + +QMap<QString, QString> proFileTagMap( const QString& text ); + +#endif |