Diffstat (limited to 'development/translation/opie-lupdate') (more/less context) (ignore whitespace changes)
-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 |
7 files changed, 1454 insertions, 0 deletions
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 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000 Trolltech AS. All rights reserved. | ||
3 | ** | ||
4 | ** This file is part of Qt Linguist. | ||
5 | ** | ||
6 | ** This file may be distributed and/or modified under the terms of the | ||
7 | ** GNU General Public License version 2 as published by the Free Software | ||
8 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
9 | ** packaging of this file. | ||
10 | ** | ||
11 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
12 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
13 | ** | ||
14 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
15 | ** | ||
16 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
17 | ** not clear to you. | ||
18 | ** | ||
19 | **********************************************************************/ | ||
20 | |||
21 | #include <metatranslator.h> | ||
22 | |||
23 | #include <qfile.h> | ||
24 | #include <qregexp.h> | ||
25 | #include <qstring.h> | ||
26 | #include <qtextstream.h> | ||
27 | #include <qvaluestack.h> | ||
28 | #include <qxml.h> | ||
29 | |||
30 | #include <ctype.h> | ||
31 | #include <errno.h> | ||
32 | #include <stdio.h> | ||
33 | #include <string.h> | ||
34 | |||
35 | /* qmake ignore Q_OBJECT */ | ||
36 | |||
37 | static const char MagicComment[] = "TRANSLATOR "; | ||
38 | |||
39 | static QMap<QCString, int> needs_Q_OBJECT; | ||
40 | static QMap<QCString, int> lacks_Q_OBJECT; | ||
41 | |||
42 | /* | ||
43 | The first part of this source file is the C++ tokenizer. We skip | ||
44 | most of C++; the only tokens that interest us are defined here. | ||
45 | Thus, the code fragment | ||
46 | |||
47 | int main() | ||
48 | { | ||
49 | printf( "Hello, world!\n" ); | ||
50 | return 0; | ||
51 | } | ||
52 | |||
53 | is broken down into the following tokens (Tok_ omitted): | ||
54 | |||
55 | Ident Ident LeftParen RightParen | ||
56 | LeftBrace | ||
57 | Ident LeftParen String RightParen Semicolon | ||
58 | return Semicolon | ||
59 | RightBrace. | ||
60 | |||
61 | The 0 doesn't produce any token. | ||
62 | */ | ||
63 | |||
64 | enum { Tok_Eof, Tok_class, Tok_namespace, Tok_return, Tok_tr, | ||
65 | Tok_trUtf8, Tok_translate, Tok_Q_OBJECT, Tok_Ident, | ||
66 | Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, | ||
67 | Tok_Gulbrandsen, Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen, | ||
68 | Tok_RightParen, Tok_Comma, Tok_Semicolon }; | ||
69 | |||
70 | /* | ||
71 | The tokenizer maintains the following global variables. The names | ||
72 | should be self-explanatory. | ||
73 | */ | ||
74 | static QCString yyFileName; | ||
75 | static int yyCh; | ||
76 | static char yyIdent[128]; | ||
77 | static size_t yyIdentLen; | ||
78 | static char yyComment[65536]; | ||
79 | static size_t yyCommentLen; | ||
80 | static char yyString[16384]; | ||
81 | static size_t yyStringLen; | ||
82 | static QValueStack<int> yySavedBraceDepth; | ||
83 | static int yyBraceDepth; | ||
84 | static int yyParenDepth; | ||
85 | static int yyLineNo; | ||
86 | static int yyCurLineNo; | ||
87 | |||
88 | // the file to read from (if reading from a file) | ||
89 | static FILE *yyInFile; | ||
90 | |||
91 | // the string to read from and current position in the string (otherwise) | ||
92 | static QString yyInStr; | ||
93 | static int yyInPos; | ||
94 | |||
95 | static int (*getChar)(); | ||
96 | |||
97 | static int getCharFromFile() | ||
98 | { | ||
99 | int c = getc( yyInFile ); | ||
100 | if ( c == '\n' ) | ||
101 | yyCurLineNo++; | ||
102 | return c; | ||
103 | } | ||
104 | |||
105 | static int getCharFromString() | ||
106 | { | ||
107 | if ( yyInPos == (int) yyInStr.length() ) { | ||
108 | return EOF; | ||
109 | } else { | ||
110 | return yyInStr[yyInPos++].latin1(); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | static void startTokenizer( const char *fileName, int (*getCharFunc)() ) | ||
115 | { | ||
116 | yyInPos = 0; | ||
117 | getChar = getCharFunc; | ||
118 | |||
119 | yyFileName = fileName; | ||
120 | yyCh = getChar(); | ||
121 | yySavedBraceDepth.clear(); | ||
122 | yyBraceDepth = 0; | ||
123 | yyParenDepth = 0; | ||
124 | yyCurLineNo = 1; | ||
125 | } | ||
126 | |||
127 | static int getToken() | ||
128 | { | ||
129 | const char tab[] = "abfnrtv"; | ||
130 | const char backTab[] = "\a\b\f\n\r\t\v"; | ||
131 | uint n; | ||
132 | |||
133 | yyIdentLen = 0; | ||
134 | yyCommentLen = 0; | ||
135 | yyStringLen = 0; | ||
136 | |||
137 | while ( yyCh != EOF ) { | ||
138 | yyLineNo = yyCurLineNo; | ||
139 | |||
140 | if ( isalpha(yyCh) || yyCh == '_' ) { | ||
141 | do { | ||
142 | if ( yyIdentLen < sizeof(yyIdent) - 1 ) | ||
143 | yyIdent[yyIdentLen++] = (char) yyCh; | ||
144 | yyCh = getChar(); | ||
145 | } while ( isalnum(yyCh) || yyCh == '_' ); | ||
146 | yyIdent[yyIdentLen] = '\0'; | ||
147 | |||
148 | switch ( yyIdent[0] ) { | ||
149 | case 'Q': | ||
150 | if ( strcmp(yyIdent + 1, "_OBJECT") == 0 ) { | ||
151 | return Tok_Q_OBJECT; | ||
152 | } else if ( strcmp(yyIdent + 1, "T_TR_NOOP") == 0 ) { | ||
153 | return Tok_tr; | ||
154 | } else if ( strcmp(yyIdent + 1, "T_TRANSLATE_NOOP") == 0 ) { | ||
155 | return Tok_translate; | ||
156 | } | ||
157 | break; | ||
158 | case 'T': | ||
159 | // TR() for when all else fails | ||
160 | if ( qstricmp(yyIdent + 1, "R") == 0 ) | ||
161 | return Tok_tr; | ||
162 | break; | ||
163 | case 'c': | ||
164 | if ( strcmp(yyIdent + 1, "lass") == 0 ) | ||
165 | return Tok_class; | ||
166 | break; | ||
167 | case 'n': | ||
168 | if ( strcmp(yyIdent + 1, "amespace") == 0 ) | ||
169 | return Tok_namespace; | ||
170 | break; | ||
171 | case 'r': | ||
172 | if ( strcmp(yyIdent + 1, "eturn") == 0 ) | ||
173 | return Tok_return; | ||
174 | break; | ||
175 | case 's': | ||
176 | if ( strcmp(yyIdent + 1, "truct") == 0 ) | ||
177 | return Tok_class; | ||
178 | break; | ||
179 | case 't': | ||
180 | if ( strcmp(yyIdent + 1, "r") == 0 ) { | ||
181 | return Tok_tr; | ||
182 | } else if ( qstrcmp(yyIdent + 1, "rUtf8") == 0 ) { | ||
183 | return Tok_trUtf8; | ||
184 | } else if ( qstrcmp(yyIdent + 1, "ranslate") == 0 ) { | ||
185 | return Tok_translate; | ||
186 | } | ||
187 | } | ||
188 | return Tok_Ident; | ||
189 | } else { | ||
190 | switch ( yyCh ) { | ||
191 | case '#': | ||
192 | /* | ||
193 | Early versions of lupdate complained about | ||
194 | unbalanced braces in the following code: | ||
195 | |||
196 | #ifdef ALPHA | ||
197 | while ( beta ) { | ||
198 | #else | ||
199 | while ( gamma ) { | ||
200 | #endif | ||
201 | delta; | ||
202 | } | ||
203 | |||
204 | The code contains, indeed, two opening braces for | ||
205 | one closing brace; yet there's no reason to panic. | ||
206 | |||
207 | The solution is to remember yyBraceDepth as it was | ||
208 | when #if, #ifdef or #ifndef was met, and to set | ||
209 | yyBraceDepth to that value when meeting #elif or | ||
210 | #else. | ||
211 | */ | ||
212 | do { | ||
213 | yyCh = getChar(); | ||
214 | } while ( isspace(yyCh) && yyCh != '\n' ); | ||
215 | |||
216 | switch ( yyCh ) { | ||
217 | case 'i': | ||
218 | yyCh = getChar(); | ||
219 | if ( yyCh == 'f' ) { | ||
220 | // if, ifdef, ifndef | ||
221 | yySavedBraceDepth.push( yyBraceDepth ); | ||
222 | } | ||
223 | break; | ||
224 | case 'e': | ||
225 | yyCh = getChar(); | ||
226 | if ( yyCh == 'l' ) { | ||
227 | // elif, else | ||
228 | if ( !yySavedBraceDepth.isEmpty() ) | ||
229 | yyBraceDepth = yySavedBraceDepth.top(); | ||
230 | } else if ( yyCh == 'n' ) { | ||
231 | // endif | ||
232 | if ( !yySavedBraceDepth.isEmpty() ) | ||
233 | yySavedBraceDepth.pop(); | ||
234 | } | ||
235 | } | ||
236 | while ( isalnum(yyCh) || yyCh == '_' ) | ||
237 | yyCh = getChar(); | ||
238 | break; | ||
239 | case '/': | ||
240 | yyCh = getChar(); | ||
241 | if ( yyCh == '/' ) { | ||
242 | do { | ||
243 | yyCh = getChar(); | ||
244 | } while ( yyCh != EOF && yyCh != '\n' ); | ||
245 | } else if ( yyCh == '*' ) { | ||
246 | bool metAster = FALSE; | ||
247 | bool metAsterSlash = FALSE; | ||
248 | |||
249 | while ( !metAsterSlash ) { | ||
250 | yyCh = getChar(); | ||
251 | if ( yyCh == EOF ) { | ||
252 | fprintf( stderr, | ||
253 | "%s: Unterminated C++ comment starting at" | ||
254 | " line %d\n", | ||
255 | (const char *) yyFileName, yyLineNo ); | ||
256 | yyComment[yyCommentLen] = '\0'; | ||
257 | return Tok_Comment; | ||
258 | } | ||
259 | if ( yyCommentLen < sizeof(yyComment) - 1 ) | ||
260 | yyComment[yyCommentLen++] = (char) yyCh; | ||
261 | |||
262 | if ( yyCh == '*' ) | ||
263 | metAster = TRUE; | ||
264 | else if ( metAster && yyCh == '/' ) | ||
265 | metAsterSlash = TRUE; | ||
266 | else | ||
267 | metAster = FALSE; | ||
268 | } | ||
269 | yyCh = getChar(); | ||
270 | yyCommentLen -= 2; | ||
271 | yyComment[yyCommentLen] = '\0'; | ||
272 | return Tok_Comment; | ||
273 | } | ||
274 | break; | ||
275 | case '"': | ||
276 | yyCh = getChar(); | ||
277 | |||
278 | while ( yyCh != EOF && yyCh != '\n' && yyCh != '"' ) { | ||
279 | if ( yyCh == '\\' ) { | ||
280 | yyCh = getChar(); | ||
281 | |||
282 | if ( yyCh == '\n' ) { | ||
283 | yyCh = getChar(); | ||
284 | } else if ( yyCh == 'x' ) { | ||
285 | QCString hex = "0"; | ||
286 | |||
287 | yyCh = getChar(); | ||
288 | while ( isxdigit(yyCh) ) { | ||
289 | hex += (char) yyCh; | ||
290 | yyCh = getChar(); | ||
291 | } | ||
292 | sscanf( hex, "%x", &n ); | ||
293 | if ( yyStringLen < sizeof(yyString) - 1 ) | ||
294 | yyString[yyStringLen++] = (char) n; | ||
295 | } else if ( yyCh >= '0' && yyCh < '8' ) { | ||
296 | QCString oct = ""; | ||
297 | |||
298 | do { | ||
299 | oct += (char) yyCh; | ||
300 | yyCh = getChar(); | ||
301 | } while ( yyCh >= '0' && yyCh < '8' ); | ||
302 | sscanf( oct, "%o", &n ); | ||
303 | if ( yyStringLen < sizeof(yyString) - 1 ) | ||
304 | yyString[yyStringLen++] = (char) n; | ||
305 | } else { | ||
306 | const char *p = strchr( tab, yyCh ); | ||
307 | if ( yyStringLen < sizeof(yyString) - 1 ) | ||
308 | yyString[yyStringLen++] = ( p == 0 ) ? | ||
309 | (char) yyCh : backTab[p - tab]; | ||
310 | yyCh = getChar(); | ||
311 | } | ||
312 | } else { | ||
313 | if ( yyStringLen < sizeof(yyString) - 1 ) | ||
314 | yyString[yyStringLen++] = (char) yyCh; | ||
315 | yyCh = getChar(); | ||
316 | } | ||
317 | } | ||
318 | yyString[yyStringLen] = '\0'; | ||
319 | |||
320 | if ( yyCh != '"' ) | ||
321 | qWarning( "%s:%d: Unterminated C++ string", | ||
322 | (const char *) yyFileName, yyLineNo ); | ||
323 | |||
324 | if ( yyCh == EOF ) { | ||
325 | return Tok_Eof; | ||
326 | } else { | ||
327 | yyCh = getChar(); | ||
328 | return Tok_String; | ||
329 | } | ||
330 | break; | ||
331 | case '-': | ||
332 | yyCh = getChar(); | ||
333 | if ( yyCh == '>' ) { | ||
334 | yyCh = getChar(); | ||
335 | return Tok_Arrow; | ||
336 | } | ||
337 | break; | ||
338 | case ':': | ||
339 | yyCh = getChar(); | ||
340 | if ( yyCh == ':' ) { | ||
341 | yyCh = getChar(); | ||
342 | return Tok_Gulbrandsen; | ||
343 | } | ||
344 | return Tok_Colon; | ||
345 | case '\'': | ||
346 | yyCh = getChar(); | ||
347 | if ( yyCh == '\\' ) | ||
348 | yyCh = getChar(); | ||
349 | |||
350 | do { | ||
351 | yyCh = getChar(); | ||
352 | } while ( yyCh != EOF && yyCh != '\'' ); | ||
353 | yyCh = getChar(); | ||
354 | break; | ||
355 | case '{': | ||
356 | yyBraceDepth++; | ||
357 | yyCh = getChar(); | ||
358 | return Tok_LeftBrace; | ||
359 | case '}': | ||
360 | yyBraceDepth--; | ||
361 | yyCh = getChar(); | ||
362 | return Tok_RightBrace; | ||
363 | case '(': | ||
364 | yyParenDepth++; | ||
365 | yyCh = getChar(); | ||
366 | return Tok_LeftParen; | ||
367 | case ')': | ||
368 | yyParenDepth--; | ||
369 | yyCh = getChar(); | ||
370 | return Tok_RightParen; | ||
371 | case ',': | ||
372 | yyCh = getChar(); | ||
373 | return Tok_Comma; | ||
374 | case ';': | ||
375 | yyCh = getChar(); | ||
376 | return Tok_Semicolon; | ||
377 | default: | ||
378 | yyCh = getChar(); | ||
379 | } | ||
380 | } | ||
381 | } | ||
382 | return Tok_Eof; | ||
383 | } | ||
384 | |||
385 | /* | ||
386 | The second part of this source file is the parser. It accomplishes | ||
387 | a very easy task: It finds all strings inside a tr() or translate() | ||
388 | call, and possibly finds out the context of the call. It supports | ||
389 | three cases: (1) the context is specified, as in | ||
390 | FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello"); | ||
391 | (2) the call appears within an inlined function; (3) the call | ||
392 | appears within a function defined outside the class definition. | ||
393 | */ | ||
394 | |||
395 | static int yyTok; | ||
396 | |||
397 | static bool match( int t ) | ||
398 | { | ||
399 | bool matches = ( yyTok == t ); | ||
400 | if ( matches ) | ||
401 | yyTok = getToken(); | ||
402 | return matches; | ||
403 | } | ||
404 | |||
405 | static bool matchString( QCString *s ) | ||
406 | { | ||
407 | bool matches = ( yyTok == Tok_String ); | ||
408 | *s = ""; | ||
409 | while ( yyTok == Tok_String ) { | ||
410 | *s += yyString; | ||
411 | yyTok = getToken(); | ||
412 | } | ||
413 | return matches; | ||
414 | } | ||
415 | |||
416 | static bool matchEncoding( bool *utf8 ) | ||
417 | { | ||
418 | if ( yyTok == Tok_Ident ) { | ||
419 | if ( strcmp(yyIdent, "QApplication") == 0 ) { | ||
420 | yyTok = getToken(); | ||
421 | if ( yyTok == Tok_Gulbrandsen ) | ||
422 | yyTok = getToken(); | ||
423 | } | ||
424 | *utf8 = QString( yyIdent ).endsWith( QString("UTF8") ); | ||
425 | yyTok = getToken(); | ||
426 | return TRUE; | ||
427 | } else { | ||
428 | return FALSE; | ||
429 | } | ||
430 | } | ||
431 | |||
432 | static void parse( MetaTranslator *tor, const char *initialContext, | ||
433 | const char *defaultContext ) | ||
434 | { | ||
435 | QMap<QCString, QCString> qualifiedContexts; | ||
436 | QStringList namespaces; | ||
437 | QCString context; | ||
438 | QCString text; | ||
439 | QCString com; | ||
440 | QCString functionContext = initialContext; | ||
441 | QCString prefix; | ||
442 | bool utf8 = FALSE; | ||
443 | bool missing_Q_OBJECT = FALSE; | ||
444 | |||
445 | yyTok = getToken(); | ||
446 | while ( yyTok != Tok_Eof ) { | ||
447 | switch ( yyTok ) { | ||
448 | case Tok_class: | ||
449 | /* | ||
450 | Partial support for inlined functions. | ||
451 | */ | ||
452 | yyTok = getToken(); | ||
453 | if ( yyBraceDepth == (int) namespaces.count() && | ||
454 | yyParenDepth == 0 ) { | ||
455 | do { | ||
456 | /* | ||
457 | This code should execute only once, but we play | ||
458 | safe with impure definitions such as | ||
459 | 'class Q_EXPORT QMessageBox', in which case | ||
460 | 'QMessageBox' is the class name, not 'Q_EXPORT'. | ||
461 | */ | ||
462 | functionContext = yyIdent; | ||
463 | yyTok = getToken(); | ||
464 | } while ( yyTok == Tok_Ident ); | ||
465 | |||
466 | while ( yyTok == Tok_Gulbrandsen ) { | ||
467 | yyTok = getToken(); | ||
468 | functionContext += "::"; | ||
469 | functionContext += yyIdent; | ||
470 | yyTok = getToken(); | ||
471 | } | ||
472 | |||
473 | if ( yyTok == Tok_Colon ) { | ||
474 | missing_Q_OBJECT = TRUE; | ||
475 | } else { | ||
476 | functionContext = defaultContext; | ||
477 | } | ||
478 | } | ||
479 | break; | ||
480 | case Tok_namespace: | ||
481 | yyTok = getToken(); | ||
482 | if ( yyTok == Tok_Ident ) { | ||
483 | QCString ns = yyIdent; | ||
484 | yyTok = getToken(); | ||
485 | if ( yyTok == Tok_LeftBrace && | ||
486 | yyBraceDepth == (int) namespaces.count() + 1 ) | ||
487 | namespaces.append( QString(ns) ); | ||
488 | } | ||
489 | break; | ||
490 | case Tok_tr: | ||
491 | case Tok_trUtf8: | ||
492 | utf8 = ( yyTok == Tok_trUtf8 ); | ||
493 | yyTok = getToken(); | ||
494 | if ( match(Tok_LeftParen) && matchString(&text) ) { | ||
495 | com = ""; | ||
496 | if ( match(Tok_RightParen) || (match(Tok_Comma) && | ||
497 | matchString(&com) && match(Tok_RightParen)) ) { | ||
498 | if ( prefix.isNull() ) { | ||
499 | context = functionContext; | ||
500 | if ( !namespaces.isEmpty() ) | ||
501 | context.prepend( (namespaces.join(QString("::")) + | ||
502 | QString("::")).latin1() ); | ||
503 | } else { | ||
504 | context = prefix; | ||
505 | } | ||
506 | prefix = (const char *) 0; | ||
507 | |||
508 | if ( qualifiedContexts.contains(context) ) | ||
509 | context = qualifiedContexts[context]; | ||
510 | tor->insert( MetaTranslatorMessage(context, text, com, | ||
511 | QString::null, utf8) ); | ||
512 | |||
513 | if ( lacks_Q_OBJECT.contains(context) ) { | ||
514 | qWarning( "%s:%d: Class '%s' lacks Q_OBJECT macro", | ||
515 | (const char *) yyFileName, yyLineNo, | ||
516 | (const char *) context ); | ||
517 | lacks_Q_OBJECT.remove( context ); | ||
518 | } else { | ||
519 | needs_Q_OBJECT.insert( context, 0 ); | ||
520 | } | ||
521 | } | ||
522 | } | ||
523 | break; | ||
524 | case Tok_translate: | ||
525 | utf8 = FALSE; | ||
526 | yyTok = getToken(); | ||
527 | if ( match(Tok_LeftParen) && | ||
528 | matchString(&context) && | ||
529 | match(Tok_Comma) && | ||
530 | matchString(&text) ) { | ||
531 | com = ""; | ||
532 | if ( match(Tok_RightParen) || | ||
533 | (match(Tok_Comma) && | ||
534 | matchString(&com) && | ||
535 | (match(Tok_RightParen) || | ||
536 | match(Tok_Comma) && | ||
537 | matchEncoding(&utf8) && | ||
538 | match(Tok_RightParen))) ) | ||
539 | tor->insert( MetaTranslatorMessage(context, text, com, | ||
540 | QString::null, utf8) ); | ||
541 | } | ||
542 | break; | ||
543 | case Tok_Q_OBJECT: | ||
544 | missing_Q_OBJECT = FALSE; | ||
545 | yyTok = getToken(); | ||
546 | break; | ||
547 | case Tok_Ident: | ||
548 | if ( !prefix.isNull() ) | ||
549 | prefix += "::"; | ||
550 | prefix += yyIdent; | ||
551 | yyTok = getToken(); | ||
552 | if ( yyTok != Tok_Gulbrandsen ) | ||
553 | prefix = (const char *) 0; | ||
554 | break; | ||
555 | case Tok_Comment: | ||
556 | com = yyComment; | ||
557 | com = com.simplifyWhiteSpace(); | ||
558 | if ( com.left(sizeof(MagicComment) - 1) == MagicComment ) { | ||
559 | com.remove( 0, sizeof(MagicComment) - 1 ); | ||
560 | int k = com.find( ' ' ); | ||
561 | if ( k == -1 ) { | ||
562 | context = com; | ||
563 | } else { | ||
564 | context = com.left( k ); | ||
565 | com.remove( 0, k + 1 ); | ||
566 | tor->insert( MetaTranslatorMessage(context, "", com, | ||
567 | QString::null, FALSE) ); | ||
568 | } | ||
569 | |||
570 | /* | ||
571 | Provide a backdoor for people using "using | ||
572 | namespace". See the manual for details. | ||
573 | */ | ||
574 | k = 0; | ||
575 | while ( (k = context.find("::", k)) != -1 ) { | ||
576 | qualifiedContexts.insert( context.mid(k + 2), context ); | ||
577 | k++; | ||
578 | } | ||
579 | } | ||
580 | yyTok = getToken(); | ||
581 | break; | ||
582 | case Tok_Arrow: | ||
583 | yyTok = getToken(); | ||
584 | if ( yyTok == Tok_tr || yyTok == Tok_trUtf8 ) | ||
585 | qWarning( "%s:%d: Cannot invoke tr() like this", | ||
586 | (const char *) yyFileName, yyLineNo ); | ||
587 | break; | ||
588 | case Tok_Gulbrandsen: | ||
589 | // at top level? | ||
590 | if ( yyBraceDepth == (int) namespaces.count() && yyParenDepth == 0 ) | ||
591 | functionContext = prefix; | ||
592 | yyTok = getToken(); | ||
593 | break; | ||
594 | case Tok_RightBrace: | ||
595 | case Tok_Semicolon: | ||
596 | if ( yyBraceDepth >= 0 && | ||
597 | yyBraceDepth + 1 == (int) namespaces.count() ) | ||
598 | namespaces.remove( namespaces.fromLast() ); | ||
599 | if ( yyBraceDepth == (int) namespaces.count() ) { | ||
600 | if ( missing_Q_OBJECT ) { | ||
601 | if ( needs_Q_OBJECT.contains(functionContext) ) { | ||
602 | qWarning( "%s:%d: Class '%s' lacks Q_OBJECT macro", | ||
603 | (const char *) yyFileName, yyLineNo, | ||
604 | (const char *) functionContext ); | ||
605 | } else { | ||
606 | lacks_Q_OBJECT.insert( functionContext, 0 ); | ||
607 | } | ||
608 | } | ||
609 | functionContext = defaultContext; | ||
610 | missing_Q_OBJECT = FALSE; | ||
611 | } | ||
612 | yyTok = getToken(); | ||
613 | break; | ||
614 | default: | ||
615 | yyTok = getToken(); | ||
616 | } | ||
617 | } | ||
618 | |||
619 | if ( yyBraceDepth != 0 ) | ||
620 | fprintf( stderr, | ||
621 | "%s: Unbalanced braces in C++ code (or abuse of the C++" | ||
622 | " preprocessor)\n", | ||
623 | (const char *) yyFileName ); | ||
624 | if ( yyParenDepth != 0 ) | ||
625 | fprintf( stderr, | ||
626 | "%s: Unbalanced parentheses in C++ code (or abuse of the C++" | ||
627 | " preprocessor)\n", | ||
628 | (const char *) yyFileName ); | ||
629 | } | ||
630 | |||
631 | void fetchtr_cpp( const char *fileName, MetaTranslator *tor, | ||
632 | const char *defaultContext, bool mustExist ) | ||
633 | { | ||
634 | yyInFile = fopen( fileName, "r" ); | ||
635 | if ( yyInFile == 0 ) { | ||
636 | if ( mustExist ) | ||
637 | fprintf( stderr, | ||
638 | "lupdate error: Cannot open C++ source file '%s': %s\n", | ||
639 | fileName, strerror(errno) ); | ||
640 | return; | ||
641 | } | ||
642 | |||
643 | startTokenizer( fileName, getCharFromFile ); | ||
644 | parse( tor, 0, defaultContext ); | ||
645 | fclose( yyInFile ); | ||
646 | } | ||
647 | |||
648 | /* | ||
649 | In addition to C++, we support Qt Designer UI files. | ||
650 | */ | ||
651 | |||
652 | /* | ||
653 | Fetches tr() calls in C++ code in UI files (inside "<function>" | ||
654 | tag). This mechanism is obsolete. | ||
655 | */ | ||
656 | void fetchtr_inlined_cpp( const char *fileName, const QString& in, | ||
657 | MetaTranslator *tor, const char *context ) | ||
658 | { | ||
659 | yyInStr = in; | ||
660 | startTokenizer( fileName, getCharFromString ); | ||
661 | parse( tor, context, 0 ); | ||
662 | yyInStr = QString::null; | ||
663 | } | ||
664 | |||
665 | class UiHandler : public QXmlDefaultHandler | ||
666 | { | ||
667 | public: | ||
668 | UiHandler( MetaTranslator *translator, const char *fileName ) | ||
669 | : tor( translator ), fname( fileName ), comment( "" ) { } | ||
670 | |||
671 | virtual bool startElement( const QString& namespaceURI, | ||
672 | const QString& localName, const QString& qName, | ||
673 | const QXmlAttributes& atts ); | ||
674 | virtual bool endElement( const QString& namespaceURI, | ||
675 | const QString& localName, const QString& qName ); | ||
676 | virtual bool characters( const QString& ch ); | ||
677 | virtual bool fatalError( const QXmlParseException& exception ); | ||
678 | |||
679 | private: | ||
680 | void flush(); | ||
681 | |||
682 | MetaTranslator *tor; | ||
683 | QCString fname; | ||
684 | QString context; | ||
685 | QString source; | ||
686 | QString comment; | ||
687 | |||
688 | QString accum; | ||
689 | }; | ||
690 | |||
691 | bool UiHandler::startElement( const QString& /* namespaceURI */, | ||
692 | const QString& /* localName */, | ||
693 | const QString& qName, | ||
694 | const QXmlAttributes& atts ) | ||
695 | { | ||
696 | if ( qName == QString("item") ) { | ||
697 | flush(); | ||
698 | if ( !atts.value(QString("text")).isEmpty() ) | ||
699 | source = atts.value( QString("text") ); | ||
700 | } else if ( qName == QString("string") ) { | ||
701 | flush(); | ||
702 | } | ||
703 | accum.truncate( 0 ); | ||
704 | return TRUE; | ||
705 | } | ||
706 | |||
707 | bool UiHandler::endElement( const QString& /* namespaceURI */, | ||
708 | const QString& /* localName */, | ||
709 | const QString& qName ) | ||
710 | { | ||
711 | accum.replace( QRegExp(QString("\r\n")), "\n" ); | ||
712 | |||
713 | if ( qName == QString("class") ) { | ||
714 | if ( context.isEmpty() ) | ||
715 | context = accum; | ||
716 | } else if ( qName == QString("string") ) { | ||
717 | source = accum; | ||
718 | } else if ( qName == QString("comment") ) { | ||
719 | comment = accum; | ||
720 | flush(); | ||
721 | } else if ( qName == QString("function") ) { | ||
722 | fetchtr_inlined_cpp( (const char *) fname, accum, tor, | ||
723 | context.latin1() ); | ||
724 | } else { | ||
725 | flush(); | ||
726 | } | ||
727 | return TRUE; | ||
728 | } | ||
729 | |||
730 | bool UiHandler::characters( const QString& ch ) | ||
731 | { | ||
732 | accum += ch; | ||
733 | return TRUE; | ||
734 | } | ||
735 | |||
736 | bool UiHandler::fatalError( const QXmlParseException& exception ) | ||
737 | { | ||
738 | QString msg; | ||
739 | msg.sprintf( "Parse error at line %d, column %d (%s).", | ||
740 | exception.lineNumber(), exception.columnNumber(), | ||
741 | exception.message().latin1() ); | ||
742 | fprintf( stderr, "XML error: %s\n", msg.latin1() ); | ||
743 | return FALSE; | ||
744 | } | ||
745 | |||
746 | void UiHandler::flush() | ||
747 | { | ||
748 | if ( !context.isEmpty() && !source.isEmpty() ) | ||
749 | tor->insert( MetaTranslatorMessage(context.utf8(), source.utf8(), | ||
750 | comment.utf8(), QString::null, | ||
751 | TRUE) ); | ||
752 | source.truncate( 0 ); | ||
753 | comment.truncate( 0 ); | ||
754 | } | ||
755 | |||
756 | void fetchtr_ui( const char *fileName, MetaTranslator *tor, | ||
757 | const char * /* defaultContext */, bool mustExist ) | ||
758 | { | ||
759 | QFile f( fileName ); | ||
760 | if ( !f.open(IO_ReadOnly) ) { | ||
761 | if ( mustExist ) | ||
762 | fprintf( stderr, "lupdate error: cannot open UI file '%s': %s\n", | ||
763 | fileName, strerror(errno) ); | ||
764 | return; | ||
765 | } | ||
766 | |||
767 | QTextStream t( &f ); | ||
768 | QXmlInputSource in( t ); | ||
769 | QXmlSimpleReader reader; | ||
770 | reader.setFeature( "http://xml.org/sax/features/namespaces", FALSE ); | ||
771 | reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", TRUE ); | ||
772 | reader.setFeature( "http://trolltech.com/xml/features/report-whitespace" | ||
773 | "-only-CharData", FALSE ); | ||
774 | QXmlDefaultHandler *hand = new UiHandler( tor, fileName ); | ||
775 | reader.setContentHandler( hand ); | ||
776 | reader.setErrorHandler( hand ); | ||
777 | |||
778 | if ( !reader.parse(in) ) | ||
779 | fprintf( stderr, "%s: Parse error in UI file\n", fileName ); | ||
780 | reader.setContentHandler( 0 ); | ||
781 | reader.setErrorHandler( 0 ); | ||
782 | delete hand; | ||
783 | f.close(); | ||
784 | } | ||
diff --git a/development/translation/opie-lupdate/lupdate b/development/translation/opie-lupdate/lupdate new file mode 100755 index 0000000..0e1604d --- a/dev/null +++ b/development/translation/opie-lupdate/lupdate | |||
Binary files differ | |||
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 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. | ||
3 | ** Copyright (C) 2003 zecke | ||
4 | ** | ||
5 | ** This file is part of Qt Linguist. | ||
6 | ** | ||
7 | ** This file may be distributed and/or modified under the terms of the | ||
8 | ** GNU General Public License version 2 as published by the Free Software | ||
9 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
10 | ** packaging of this file. | ||
11 | ** | ||
12 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
13 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
14 | ** | ||
15 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
16 | ** | ||
17 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
18 | ** not clear to you. | ||
19 | ** | ||
20 | **********************************************************************/ | ||
21 | |||
22 | #include <metatranslator.h> | ||
23 | #include <proparser.h> | ||
24 | #include <opie.h> | ||
25 | |||
26 | #include <qfile.h> | ||
27 | #include <qfileinfo.h> | ||
28 | #include <qstring.h> | ||
29 | #include <qstringlist.h> | ||
30 | #include <qtextstream.h> | ||
31 | |||
32 | #include <errno.h> | ||
33 | #include <string.h> | ||
34 | |||
35 | // defined in fetchtr.cpp | ||
36 | extern void fetchtr_cpp( const char *fileName, MetaTranslator *tor, | ||
37 | const char *defaultContext, bool mustExist ); | ||
38 | extern void fetchtr_ui( const char *fileName, MetaTranslator *tor, | ||
39 | const char *defaultContext, bool mustExist ); | ||
40 | |||
41 | // defined in merge.cpp | ||
42 | extern void merge( MetaTranslator *tor, const MetaTranslator *virginTor, | ||
43 | bool verbose ); | ||
44 | |||
45 | typedef QValueList<MetaTranslatorMessage> TML; | ||
46 | |||
47 | static const char* LUPDATE_VERSION = "0.1"; | ||
48 | |||
49 | static void printUsage() | ||
50 | { | ||
51 | fprintf( stderr, "Usage:\n" | ||
52 | " opie-lupdate [options] project-file\n" | ||
53 | " opie-lupdate [options] source-files -ts ts-files\n" | ||
54 | "Options:\n" | ||
55 | " -opie The OPIE base dir if not supplied $OPIEDIR will be taken\n" | ||
56 | " -help Display this information and exit\n" | ||
57 | " -noobsolete\n" | ||
58 | " Drop all obsolete strings\n" | ||
59 | " -verbose\n" | ||
60 | " Explain what is being done\n" | ||
61 | " -version\n" | ||
62 | " Display the version of lupdate and exit\n" ); | ||
63 | } | ||
64 | |||
65 | static void updateTsFiles( const MetaTranslator& fetchedTor, | ||
66 | const QString& opiedir, | ||
67 | const QStringList& languages, | ||
68 | const QString& basename, | ||
69 | const QString& codec, | ||
70 | bool noObsolete, bool verbose ) | ||
71 | { | ||
72 | QStringList::ConstIterator it = languages.begin(); | ||
73 | for ( ; it != languages.end(); ++it ) { | ||
74 | QString fileName = opiedir + "/i18n/" + (*it) + "/" + basename; | ||
75 | MetaTranslator tor; | ||
76 | tor.load( fileName ); | ||
77 | if ( !codec.isEmpty() ) | ||
78 | tor.setCodec( codec ); | ||
79 | if ( verbose ) | ||
80 | fprintf( stderr, "Updating '%s'...\n", fileName.latin1() ); | ||
81 | merge( &tor, &fetchedTor, verbose ); | ||
82 | if ( noObsolete ) | ||
83 | tor.stripObsoleteMessages(); | ||
84 | tor.stripEmptyContexts(); | ||
85 | if ( !tor.save(fileName) ) | ||
86 | fprintf( stderr, "lupdate error: Cannot save '%s': %s\n", | ||
87 | fileName.latin1(), strerror(errno) ); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | int main( int argc, char **argv ) | ||
92 | { | ||
93 | QString defaultContext = "@default"; | ||
94 | MetaTranslator fetchedTor; | ||
95 | QCString codec; | ||
96 | QStringList tsFileNames; | ||
97 | QString opiedir; | ||
98 | QString translationBase; | ||
99 | QString target; | ||
100 | |||
101 | bool verbose = FALSE; | ||
102 | bool noObsolete = FALSE; | ||
103 | bool metSomething = FALSE; | ||
104 | bool isLib = FALSE; | ||
105 | int numFiles = 0; | ||
106 | |||
107 | int i; | ||
108 | |||
109 | QStringList languageList = OPIE::self()->languageList(opiedir); | ||
110 | |||
111 | for ( i = 1; i < argc; i++ ) { | ||
112 | if ( qstrcmp(argv[i], "-help") == 0 ) { | ||
113 | printUsage(); | ||
114 | return 0; | ||
115 | } else if ( qstrcmp(argv[i], "-noobsolete") == 0 ) { | ||
116 | noObsolete = TRUE; | ||
117 | continue; | ||
118 | } else if ( qstrcmp(argv[i], "-verbose") == 0 ) { | ||
119 | verbose = TRUE; | ||
120 | continue; | ||
121 | } else if ( qstrcmp(argv[i], "-version") == 0 ) { | ||
122 | fprintf( stderr, "lupdate version %s\n", LUPDATE_VERSION ); | ||
123 | return 0; | ||
124 | } else if ( qstrcmp(argv[i], "-opie") == 0 ) { | ||
125 | if( i+1 < argc ) { | ||
126 | opiedir = argv[i+1]; | ||
127 | languageList = OPIE::self()->languageList(opiedir); | ||
128 | } | ||
129 | i++; // UGLY but we want to skip the next argument | ||
130 | continue; | ||
131 | } | ||
132 | |||
133 | numFiles++; | ||
134 | |||
135 | QString fullText; | ||
136 | |||
137 | QFile f( argv[i] ); | ||
138 | if ( !f.open(IO_ReadOnly) ) { | ||
139 | fprintf( stderr, "lupdate error: Cannot open file '%s': %s\n", | ||
140 | argv[i], strerror(errno) ); | ||
141 | return 1; | ||
142 | } | ||
143 | |||
144 | QTextStream t( &f ); | ||
145 | fullText = t.read(); | ||
146 | f.close(); | ||
147 | |||
148 | fetchedTor = MetaTranslator(); | ||
149 | codec.truncate( 0 ); | ||
150 | tsFileNames.clear(); | ||
151 | isLib = FALSE; | ||
152 | |||
153 | QMap<QString, QString> tagMap = proFileTagMap( fullText ); | ||
154 | QMap<QString, QString>::Iterator it; | ||
155 | |||
156 | for ( it = tagMap.begin(); it != tagMap.end(); ++it ) { | ||
157 | QStringList toks = QStringList::split( ' ', it.data() ); | ||
158 | QStringList::Iterator t; | ||
159 | |||
160 | for ( t = toks.begin(); t != toks.end(); ++t ) { | ||
161 | if ( it.key() == "HEADERS" || it.key() == "SOURCES" ) { | ||
162 | fetchtr_cpp( *t, &fetchedTor, defaultContext, TRUE ); | ||
163 | metSomething = TRUE; | ||
164 | } else if ( it.key() == "INTERFACES" || | ||
165 | it.key() == "FORMS" ) { | ||
166 | fetchtr_ui( *t, &fetchedTor, defaultContext, TRUE ); | ||
167 | fetchtr_cpp( *t + ".h", &fetchedTor, defaultContext, | ||
168 | FALSE ); | ||
169 | metSomething = TRUE; | ||
170 | } else if ( it.key() == "TRANSLATIONS" ) { | ||
171 | // we do not care for that attribute anymore | ||
172 | //tsFileNames.append( *t ); | ||
173 | metSomething = TRUE; | ||
174 | } else if ( it.key() == "CODEC" ) { | ||
175 | codec = (*t).latin1(); | ||
176 | } else if ( it.key() == "TARGET" ) { | ||
177 | target = *t; | ||
178 | metSomething = TRUE; | ||
179 | } else if ( it.key() == "TEMPLATE" ) { | ||
180 | if ( (*t).stripWhiteSpace().lower() == "lib" ) | ||
181 | isLib = true; | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | /** | ||
186 | * We know the $OPIEDIR or have opiedir | ||
187 | * we've a list of languages (de,en,gb,foo,bar) | ||
188 | * we've got the TARGET and we no it's the lib | ||
189 | * so let's do that | ||
190 | * $OPIEDIR/language[i]/ifLibAppendLib$TARGET.ts | ||
191 | */ | ||
192 | qWarning("TARGET %s IsLib:%d", target.latin1(), isLib ); | ||
193 | qWarning("LANGS %s", languageList.join(";").latin1() ); | ||
194 | qWarning("OPIEDIR %s", OPIE::self()->opieDir(opiedir).latin1() ); | ||
195 | if (isLib ) | ||
196 | target.prepend("lib"); | ||
197 | target += ".ts"; | ||
198 | updateTsFiles( fetchedTor, OPIE::self()->opieDir(opiedir), | ||
199 | languageList, target, codec, noObsolete, verbose ); | ||
200 | |||
201 | if ( !metSomething ) { | ||
202 | fprintf( stderr, | ||
203 | "lupdate warning: File '%s' does not look like a" | ||
204 | " project file\n", | ||
205 | argv[i] ); | ||
206 | } | ||
207 | |||
208 | } | ||
209 | |||
210 | if ( numFiles == 0 ) { | ||
211 | printUsage(); | ||
212 | return 1; | ||
213 | } | ||
214 | return 0; | ||
215 | } | ||
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 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000 Trolltech AS. All rights reserved. | ||
3 | ** | ||
4 | ** This file is part of Qt Linguist. | ||
5 | ** | ||
6 | ** This file may be distributed and/or modified under the terms of the | ||
7 | ** GNU General Public License version 2 as published by the Free Software | ||
8 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
9 | ** packaging of this file. | ||
10 | ** | ||
11 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
12 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
13 | ** | ||
14 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
15 | ** | ||
16 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
17 | ** not clear to you. | ||
18 | ** | ||
19 | **********************************************************************/ | ||
20 | |||
21 | #include <metatranslator.h> | ||
22 | |||
23 | // defined in numberh.cpp | ||
24 | extern void applyNumberHeuristic( MetaTranslator *tor, bool verbose ); | ||
25 | // defined in sametexth.cpp | ||
26 | extern void applySameTextHeuristic( MetaTranslator *tor, bool verbose ); | ||
27 | |||
28 | typedef QValueList<MetaTranslatorMessage> TML; | ||
29 | |||
30 | /* | ||
31 | Merges two MetaTranslator objects into the first one. The first one | ||
32 | is a set of source texts and translations for a previous version of | ||
33 | the internationalized program; the second one is a set of fresh | ||
34 | source texts newly extracted from the source code, without any | ||
35 | translation yet. | ||
36 | */ | ||
37 | |||
38 | void merge( MetaTranslator *tor, const MetaTranslator *virginTor, bool verbose ) | ||
39 | { | ||
40 | int known = 0; | ||
41 | int neww = 0; | ||
42 | int obsoleted = 0; | ||
43 | TML all = tor->messages(); | ||
44 | TML::Iterator it; | ||
45 | |||
46 | /* | ||
47 | The types of all the messages from the vernacular translator | ||
48 | are updated according to the virgin translator. | ||
49 | */ | ||
50 | for ( it = all.begin(); it != all.end(); ++it ) { | ||
51 | MetaTranslatorMessage::Type newType; | ||
52 | MetaTranslatorMessage m = *it; | ||
53 | |||
54 | // skip context comment | ||
55 | if ( !QCString((*it).sourceText()).isEmpty() ) { | ||
56 | if ( !virginTor->contains((*it).context(), (*it).sourceText(), | ||
57 | (*it).comment()) ) { | ||
58 | newType = MetaTranslatorMessage::Obsolete; | ||
59 | if ( m.type() != MetaTranslatorMessage::Obsolete ) | ||
60 | obsoleted++; | ||
61 | } else { | ||
62 | switch ( m.type() ) { | ||
63 | case MetaTranslatorMessage::Finished: | ||
64 | newType = MetaTranslatorMessage::Finished; | ||
65 | known++; | ||
66 | break; | ||
67 | case MetaTranslatorMessage::Unfinished: | ||
68 | newType = MetaTranslatorMessage::Unfinished; | ||
69 | known++; | ||
70 | break; | ||
71 | case MetaTranslatorMessage::Obsolete: | ||
72 | newType = MetaTranslatorMessage::Unfinished; | ||
73 | neww++; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | if ( newType != m.type() ) { | ||
78 | m.setType( newType ); | ||
79 | tor->insert( m ); | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | /* | ||
85 | Messages found only in the virgin translator are added to the | ||
86 | vernacular translator. Among these are all the context comments. | ||
87 | */ | ||
88 | all = virginTor->messages(); | ||
89 | |||
90 | for ( it = all.begin(); it != all.end(); ++it ) { | ||
91 | if ( !tor->contains((*it).context(), (*it).sourceText(), | ||
92 | (*it).comment()) ) { | ||
93 | tor->insert( *it ); | ||
94 | if ( !QCString((*it).sourceText()).isEmpty() ) | ||
95 | neww++; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | /* | ||
100 | The same-text heuristic handles cases where a message has an | ||
101 | obsolete counterpart with a different context or comment. | ||
102 | */ | ||
103 | applySameTextHeuristic( tor, verbose ); | ||
104 | |||
105 | /* | ||
106 | The number heuristic handles cases where a message has an | ||
107 | obsolete counterpart with mostly numbers differing in the | ||
108 | source text. | ||
109 | */ | ||
110 | applyNumberHeuristic( tor, verbose ); | ||
111 | |||
112 | if ( verbose ) | ||
113 | fprintf( stderr, " %d known, %d new and %d obsoleted messages\n", known, | ||
114 | neww, obsoleted ); | ||
115 | } | ||
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 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. | ||
3 | ** | ||
4 | ** This file is part of Qt Linguist. | ||
5 | ** | ||
6 | ** This file may be distributed and/or modified under the terms of the | ||
7 | ** GNU General Public License version 2 as published by the Free Software | ||
8 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
9 | ** packaging of this file. | ||
10 | ** | ||
11 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
12 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
13 | ** | ||
14 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
15 | ** | ||
16 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
17 | ** not clear to you. | ||
18 | ** | ||
19 | **********************************************************************/ | ||
20 | |||
21 | #include <metatranslator.h> | ||
22 | |||
23 | #include <qmemarray.h> | ||
24 | #include <qcstring.h> | ||
25 | #include <qmap.h> | ||
26 | #include <qstringlist.h> | ||
27 | |||
28 | #include <ctype.h> | ||
29 | |||
30 | typedef QMap<QCString, MetaTranslatorMessage> TMM; | ||
31 | typedef QValueList<MetaTranslatorMessage> TML; | ||
32 | |||
33 | static bool isDigitFriendly( int c ) | ||
34 | { | ||
35 | return ispunct( c ) || isspace( c ); | ||
36 | } | ||
37 | |||
38 | static int numberLength( const char *s ) | ||
39 | { | ||
40 | int i = 0; | ||
41 | |||
42 | if ( isdigit(s[0]) ) { | ||
43 | do { | ||
44 | i++; | ||
45 | } while ( isdigit(s[i]) || | ||
46 | (isDigitFriendly(s[i]) && | ||
47 | (isdigit(s[i + 1]) || | ||
48 | (isDigitFriendly(s[i + 1]) && isdigit(s[i + 2])))) ); | ||
49 | } | ||
50 | return i; | ||
51 | } | ||
52 | |||
53 | /* | ||
54 | Returns a version of 'key' where all numbers have been replaced by zeroes. If | ||
55 | there were none, returns "". | ||
56 | */ | ||
57 | static QCString zeroKey( const char *key ) | ||
58 | { | ||
59 | QCString zeroed( strlen(key) + 1 ); | ||
60 | char *z = zeroed.data(); | ||
61 | int i = 0, j = 0; | ||
62 | int len; | ||
63 | bool metSomething = FALSE; | ||
64 | |||
65 | while ( key[i] != '\0' ) { | ||
66 | len = numberLength( key + i ); | ||
67 | if ( len > 0 ) { | ||
68 | i += len; | ||
69 | z[j++] = '0'; | ||
70 | metSomething = TRUE; | ||
71 | } else { | ||
72 | z[j++] = key[i++]; | ||
73 | } | ||
74 | } | ||
75 | z[j] = '\0'; | ||
76 | |||
77 | if ( metSomething ) | ||
78 | return zeroed; | ||
79 | else | ||
80 | return ""; | ||
81 | } | ||
82 | |||
83 | static QString translationAttempt( const QString& oldTranslation, | ||
84 | const char *oldSource, | ||
85 | const char *newSource ) | ||
86 | { | ||
87 | int p = zeroKey( oldSource ).contains( '0' ); | ||
88 | int oldSourceLen = qstrlen( oldSource ); | ||
89 | QString attempt; | ||
90 | QStringList oldNumbers; | ||
91 | QStringList newNumbers; | ||
92 | QMemArray<bool> met( p ); | ||
93 | QMemArray<int> matchedYet( p ); | ||
94 | int i, j; | ||
95 | int k = 0, ell, best; | ||
96 | int m, n; | ||
97 | int pass; | ||
98 | |||
99 | /* | ||
100 | This algorithm is hard to follow, so we'll consider an example | ||
101 | all along: oldTranslation is "XeT 3.0", oldSource is "TeX 3.0" | ||
102 | and newSource is "XeT 3.1". | ||
103 | |||
104 | First, we set up two tables: oldNumbers and newNumbers. In our | ||
105 | example, oldNumber[0] is "3.0" and newNumber[0] is "3.1". | ||
106 | */ | ||
107 | for ( i = 0, j = 0; i < oldSourceLen; i++, j++ ) { | ||
108 | m = numberLength( oldSource + i ); | ||
109 | n = numberLength( newSource + j ); | ||
110 | if ( m > 0 ) { | ||
111 | oldNumbers.append( QCString(oldSource + i, m + 1) ); | ||
112 | newNumbers.append( QCString(newSource + j, n + 1) ); | ||
113 | i += m; | ||
114 | j += n; | ||
115 | met[k] = FALSE; | ||
116 | matchedYet[k] = 0; | ||
117 | k++; | ||
118 | } | ||
119 | } | ||
120 | |||
121 | /* | ||
122 | We now go over the old translation, "XeT 3.0", one letter at a | ||
123 | time, looking for numbers found in oldNumbers. Whenever such a | ||
124 | number is met, it is replaced with its newNumber equivalent. In | ||
125 | our example, the "3.0" of "XeT 3.0" becomes "3.1". | ||
126 | */ | ||
127 | for ( i = 0; i < (int) oldTranslation.length(); i++ ) { | ||
128 | attempt += oldTranslation[i]; | ||
129 | for ( k = 0; k < p; k++ ) { | ||
130 | if ( oldTranslation[i] == oldNumbers[k][matchedYet[k]] ) | ||
131 | matchedYet[k]++; | ||
132 | else | ||
133 | matchedYet[k] = 0; | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | Let's find out if the last character ended a match. We make | ||
138 | two passes over the data. In the first pass, we try to | ||
139 | match only numbers that weren't matched yet; if that fails, | ||
140 | the second pass does the trick. This is useful in some | ||
141 | suspicious cases, flagged below. | ||
142 | */ | ||
143 | for ( pass = 0; pass < 2; pass++ ) { | ||
144 | best = p; // an impossible value | ||
145 | for ( k = 0; k < p; k++ ) { | ||
146 | if ( (!met[k] || pass > 0) && | ||
147 | matchedYet[k] == (int) oldNumbers[k].length() && | ||
148 | numberLength(oldTranslation.latin1() + (i + 1) - | ||
149 | matchedYet[k]) == matchedYet[k] ) { | ||
150 | // the longer the better | ||
151 | if ( best == p || matchedYet[k] > matchedYet[best] ) | ||
152 | best = k; | ||
153 | } | ||
154 | } | ||
155 | if ( best != p ) { | ||
156 | attempt.truncate( attempt.length() - matchedYet[best] ); | ||
157 | attempt += newNumbers[best]; | ||
158 | met[best] = TRUE; | ||
159 | for ( k = 0; k < p; k++ ) | ||
160 | matchedYet[k] = 0; | ||
161 | break; | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | /* | ||
167 | We flag two kinds of suspicious cases. They are identified as | ||
168 | such with comments such as "{2000?}" at the end. | ||
169 | |||
170 | Example of the first kind: old source text "TeX 3.0" translated | ||
171 | as "XeT 2.0" is flagged "TeX 2.0 {3.0?}", no matter what the | ||
172 | new text is. | ||
173 | */ | ||
174 | for ( k = 0; k < p; k++ ) { | ||
175 | if ( !met[k] ) | ||
176 | attempt += QString( " {" ) + newNumbers[k] + QString( "?}" ); | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | Example of the second kind: "1 of 1" translated as "1 af 1", | ||
181 | with new source text "1 of 2", generates "1 af 2 {1 or 2?}" | ||
182 | because it's not clear which of "1 af 2" and "2 af 1" is right. | ||
183 | */ | ||
184 | for ( k = 0; k < p; k++ ) { | ||
185 | for ( ell = 0; ell < p; ell++ ) { | ||
186 | if ( k != ell && oldNumbers[k] == oldNumbers[ell] && | ||
187 | newNumbers[k] < newNumbers[ell] ) | ||
188 | attempt += QString( " {" ) + newNumbers[k] + QString( " or " ) + | ||
189 | newNumbers[ell] + QString( "?}" ); | ||
190 | } | ||
191 | } | ||
192 | return attempt; | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | Augments a MetaTranslator with translations easily derived from | ||
197 | similar existing (probably obsolete) translations. | ||
198 | |||
199 | For example, if "TeX 3.0" is translated as "XeT 3.0" and "TeX 3.1" | ||
200 | has no translation, "XeT 3.1" is added to the translator and is | ||
201 | marked Unfinished. | ||
202 | */ | ||
203 | void applyNumberHeuristic( MetaTranslator *tor, bool verbose ) | ||
204 | { | ||
205 | TMM translated, untranslated; | ||
206 | TMM::Iterator t, u; | ||
207 | TML all = tor->messages(); | ||
208 | TML::Iterator it; | ||
209 | int inserted = 0; | ||
210 | |||
211 | for ( it = all.begin(); it != all.end(); ++it ) { | ||
212 | if ( (*it).type() == MetaTranslatorMessage::Unfinished ) { | ||
213 | if ( (*it).translation().isEmpty() ) | ||
214 | untranslated.insert( zeroKey((*it).sourceText()), *it ); | ||
215 | } else if ( !(*it).translation().isEmpty() ) { | ||
216 | translated.insert( zeroKey((*it).sourceText()), *it ); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | for ( u = untranslated.begin(); u != untranslated.end(); ++u ) { | ||
221 | t = translated.find( u.key() ); | ||
222 | if ( t != translated.end() && !t.key().isEmpty() && | ||
223 | qstrcmp((*t).sourceText(), (*u).sourceText()) != 0 ) { | ||
224 | MetaTranslatorMessage m( *u ); | ||
225 | m.setTranslation( translationAttempt((*t).translation(), | ||
226 | (*t).sourceText(), | ||
227 | (*u).sourceText()) ); | ||
228 | tor->insert( m ); | ||
229 | inserted++; | ||
230 | } | ||
231 | } | ||
232 | if ( verbose && inserted != 0 ) | ||
233 | fprintf( stderr, " number heuristic provided %d translation%s\n", | ||
234 | inserted, inserted == 1 ? "" : "s" ); | ||
235 | } | ||
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 @@ | |||
1 | TEMPLATE= app | ||
2 | CONFIG += qt warn_on console | ||
3 | HEADERS = ../shared/metatranslator.h \ | ||
4 | ../shared/proparser.h \ | ||
5 | ../shared/opie.h | ||
6 | |||
7 | SOURCES = fetchtr.cpp \ | ||
8 | main.cpp \ | ||
9 | merge.cpp \ | ||
10 | numberh.cpp \ | ||
11 | sametexth.cpp \ | ||
12 | ../shared/metatranslator.cpp \ | ||
13 | ../shared/proparser.cpp \ | ||
14 | ../shared/opie.cpp | ||
15 | |||
16 | DEFINES += QT_INTERNAL_XML | ||
17 | |||
18 | TARGET = opie-lupdate | ||
19 | INCLUDEPATH+= ../shared | ||
20 | #DESTDIR = | ||
21 | |||
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 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. | ||
3 | ** | ||
4 | ** This file is part of Qt Linguist. | ||
5 | ** | ||
6 | ** This file may be distributed and/or modified under the terms of the | ||
7 | ** GNU General Public License version 2 as published by the Free Software | ||
8 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
9 | ** packaging of this file. | ||
10 | ** | ||
11 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
12 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
13 | ** | ||
14 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
15 | ** | ||
16 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
17 | ** not clear to you. | ||
18 | ** | ||
19 | **********************************************************************/ | ||
20 | |||
21 | #include <metatranslator.h> | ||
22 | |||
23 | #include <qcstring.h> | ||
24 | #include <qmap.h> | ||
25 | |||
26 | typedef QMap<QCString, MetaTranslatorMessage> TMM; | ||
27 | typedef QValueList<MetaTranslatorMessage> TML; | ||
28 | |||
29 | /* | ||
30 | Augments a MetaTranslator with trivially derived translations. | ||
31 | |||
32 | For example, if "Enabled:" is consistendly translated as "Eingeschaltet:" no | ||
33 | matter the context or the comment, "Eingeschaltet:" is added as the | ||
34 | translation of any untranslated "Enabled:" text and is marked Unfinished. | ||
35 | */ | ||
36 | |||
37 | void applySameTextHeuristic( MetaTranslator *tor, bool verbose ) | ||
38 | { | ||
39 | TMM translated; | ||
40 | TMM avoid; | ||
41 | TMM::Iterator t; | ||
42 | TML untranslated; | ||
43 | TML::Iterator u; | ||
44 | TML all = tor->messages(); | ||
45 | TML::Iterator it; | ||
46 | int inserted = 0; | ||
47 | |||
48 | for ( it = all.begin(); it != all.end(); ++it ) { | ||
49 | if ( (*it).type() == MetaTranslatorMessage::Unfinished ) { | ||
50 | if ( (*it).translation().isEmpty() ) | ||
51 | untranslated.append( *it ); | ||
52 | } else { | ||
53 | QCString key = (*it).sourceText(); | ||
54 | t = translated.find( key ); | ||
55 | if ( t != translated.end() ) { | ||
56 | /* | ||
57 | The same source text is translated at least two | ||
58 | different ways. Do nothing then. | ||
59 | */ | ||
60 | if ( (*t).translation() != (*it).translation() ) { | ||
61 | translated.remove( key ); | ||
62 | avoid.insert( key, *it ); | ||
63 | } | ||
64 | } else if ( !avoid.contains(key) && | ||
65 | !(*it).translation().isEmpty() ) { | ||
66 | translated.insert( key, *it ); | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | for ( u = untranslated.begin(); u != untranslated.end(); ++u ) { | ||
72 | QCString key = (*u).sourceText(); | ||
73 | t = translated.find( key ); | ||
74 | if ( t != translated.end() ) { | ||
75 | MetaTranslatorMessage m( *u ); | ||
76 | m.setTranslation( (*t).translation() ); | ||
77 | tor->insert( m ); | ||
78 | inserted++; | ||
79 | } | ||
80 | } | ||
81 | if ( verbose && inserted != 0 ) | ||
82 | fprintf( stderr, " same-text heuristic provided %d translation%s\n", | ||
83 | inserted, inserted == 1 ? "" : "s" ); | ||
84 | } | ||