source: trunk/tools/linguist/lupdate/java.cpp@ 1147

Last change on this file since 1147 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 19.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the Qt Linguist of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "lupdate.h"
43
44#include <translator.h>
45
46#include <QtCore/QDebug>
47#include <QtCore/QFile>
48#include <QtCore/QRegExp>
49#include <QtCore/QStack>
50#include <QtCore/QStack>
51#include <QtCore/QString>
52#include <QtCore/QTextCodec>
53#include <QtCore/QCoreApplication>
54
55#include <iostream>
56
57#include <ctype.h>
58
59QT_BEGIN_NAMESPACE
60
61class LU {
62 Q_DECLARE_TR_FUNCTIONS(LUpdate)
63};
64
65enum { Tok_Eof, Tok_class, Tok_return, Tok_tr,
66 Tok_translate, Tok_Ident, Tok_Package,
67 Tok_Comment, Tok_String, Tok_Colon, Tok_Dot,
68 Tok_LeftBrace, Tok_RightBrace, Tok_LeftParen,
69 Tok_RightParen, Tok_Comma, Tok_Semicolon,
70 Tok_Integer, Tok_Plus, Tok_PlusPlus, Tok_PlusEq, Tok_null };
71
72class Scope
73{
74 public:
75 QString name;
76 enum Type {Clazz, Function, Other} type;
77 int line;
78
79 Scope(const QString & name, Type type, int line) :
80 name(name),
81 type(type),
82 line(line)
83 {}
84
85 ~Scope()
86 {}
87};
88
89/*
90 The tokenizer maintains the following global variables. The names
91 should be self-explanatory.
92*/
93
94static QString yyFileName;
95static QChar yyCh;
96static QString yyIdent;
97static QString yyComment;
98static QString yyString;
99
100
101static qlonglong yyInteger;
102static int yyParenDepth;
103static int yyLineNo;
104static int yyCurLineNo;
105static int yyParenLineNo;
106static int yyTok;
107
108// the string to read from and current position in the string
109static QString yyInStr;
110static int yyInPos;
111
112// The parser maintains the following global variables.
113static QString yyPackage;
114static QStack<Scope*> yyScope;
115static QString yyDefaultContext;
116
117std::ostream &yyMsg(int line = 0)
118{
119 return std::cerr << qPrintable(yyFileName) << ':' << (line ? line : yyLineNo) << ": ";
120}
121
122static QChar getChar()
123{
124 if (yyInPos >= yyInStr.size())
125 return EOF;
126 QChar c = yyInStr[yyInPos++];
127 if (c.unicode() == '\n')
128 ++yyCurLineNo;
129 return c.unicode();
130}
131
132static int getToken()
133{
134 const char tab[] = "bfnrt\"\'\\";
135 const char backTab[] = "\b\f\n\r\t\"\'\\";
136
137 yyIdent.clear();
138 yyComment.clear();
139 yyString.clear();
140
141 while ( yyCh != EOF ) {
142 yyLineNo = yyCurLineNo;
143
144 if ( yyCh.isLetter() || yyCh.toLatin1() == '_' ) {
145 do {
146 yyIdent.append(yyCh);
147 yyCh = getChar();
148 } while ( yyCh.isLetterOrNumber() || yyCh.toLatin1() == '_' );
149
150 if (yyTok != Tok_Dot) {
151 switch ( yyIdent.at(0).toLatin1() ) {
152 case 'r':
153 if ( yyIdent == QLatin1String("return") )
154 return Tok_return;
155 break;
156 case 'c':
157 if ( yyIdent == QLatin1String("class") )
158 return Tok_class;
159 break;
160 case 'n':
161 if ( yyIdent == QLatin1String("null") )
162 return Tok_null;
163 break;
164 }
165 }
166 switch ( yyIdent.at(0).toLatin1() ) {
167 case 'T':
168 // TR() for when all else fails
169 if ( yyIdent == QLatin1String("TR") )
170 return Tok_tr;
171 break;
172 case 'p':
173 if( yyIdent == QLatin1String("package") )
174 return Tok_Package;
175 break;
176 case 't':
177 if ( yyIdent == QLatin1String("tr") )
178 return Tok_tr;
179 if ( yyIdent == QLatin1String("translate") )
180 return Tok_translate;
181 }
182 return Tok_Ident;
183 } else {
184 switch ( yyCh.toLatin1() ) {
185
186 case '/':
187 yyCh = getChar();
188 if ( yyCh == QLatin1Char('/') ) {
189 do {
190 yyCh = getChar();
191 if (yyCh == EOF)
192 break;
193 yyComment.append(yyCh);
194 } while (yyCh != QLatin1Char('\n'));
195 return Tok_Comment;
196
197 } else if ( yyCh == QLatin1Char('*') ) {
198 bool metAster = false;
199 bool metAsterSlash = false;
200
201 while ( !metAsterSlash ) {
202 yyCh = getChar();
203 if ( yyCh == EOF ) {
204 yyMsg() << qPrintable(LU::tr("Unterminated Java comment.\n"));
205 return Tok_Comment;
206 }
207
208 yyComment.append( yyCh );
209
210 if ( yyCh == QLatin1Char('*') )
211 metAster = true;
212 else if ( metAster && yyCh == QLatin1Char('/') )
213 metAsterSlash = true;
214 else
215 metAster = false;
216 }
217 yyComment.chop(2);
218 yyCh = getChar();
219
220 return Tok_Comment;
221 }
222 break;
223 case '"':
224 yyCh = getChar();
225
226 while ( yyCh != EOF && yyCh != QLatin1Char('\n') && yyCh != QLatin1Char('"') ) {
227 if ( yyCh == QLatin1Char('\\') ) {
228 yyCh = getChar();
229 if ( yyCh == QLatin1Char('u') ) {
230 yyCh = getChar();
231 uint unicode(0);
232 for (int i = 4; i > 0; --i) {
233 unicode = unicode << 4;
234 if( yyCh.isDigit() ) {
235 unicode += yyCh.digitValue();
236 }
237 else {
238 int sub(yyCh.toLower().toAscii() - 87);
239 if( sub > 15 || sub < 10) {
240 yyMsg() << qPrintable(LU::tr("Invalid Unicode value.\n"));
241 break;
242 }
243 unicode += sub;
244 }
245 yyCh = getChar();
246 }
247 yyString.append(QChar(unicode));
248 }
249 else if ( yyCh == QLatin1Char('\n') ) {
250 yyCh = getChar();
251 }
252 else {
253 yyString.append( QLatin1Char(backTab[strchr( tab, yyCh.toAscii() ) - tab]) );
254 yyCh = getChar();
255 }
256 } else {
257 yyString.append(yyCh);
258 yyCh = getChar();
259 }
260 }
261
262 if ( yyCh != QLatin1Char('"') )
263 yyMsg() << qPrintable(LU::tr("Unterminated string.\n"));
264
265 yyCh = getChar();
266
267 return Tok_String;
268
269 case ':':
270 yyCh = getChar();
271 return Tok_Colon;
272 case '\'':
273 yyCh = getChar();
274
275 if ( yyCh == QLatin1Char('\\') )
276 yyCh = getChar();
277 do {
278 yyCh = getChar();
279 } while ( yyCh != EOF && yyCh != QLatin1Char('\'') );
280 yyCh = getChar();
281 break;
282 case '{':
283 yyCh = getChar();
284 return Tok_LeftBrace;
285 case '}':
286 yyCh = getChar();
287 return Tok_RightBrace;
288 case '(':
289 if (yyParenDepth == 0)
290 yyParenLineNo = yyCurLineNo;
291 yyParenDepth++;
292 yyCh = getChar();
293 return Tok_LeftParen;
294 case ')':
295 if (yyParenDepth == 0)
296 yyParenLineNo = yyCurLineNo;
297 yyParenDepth--;
298 yyCh = getChar();
299 return Tok_RightParen;
300 case ',':
301 yyCh = getChar();
302 return Tok_Comma;
303 case '.':
304 yyCh = getChar();
305 return Tok_Dot;
306 case ';':
307 yyCh = getChar();
308 return Tok_Semicolon;
309 case '+':
310 yyCh = getChar();
311 if (yyCh == QLatin1Char('+')) {
312 yyCh = getChar();
313 return Tok_PlusPlus;
314 }
315 if( yyCh == QLatin1Char('=') ){
316 yyCh = getChar();
317 return Tok_PlusEq;
318 }
319 return Tok_Plus;
320 case '0':
321 case '1':
322 case '2':
323 case '3':
324 case '4':
325 case '5':
326 case '6':
327 case '7':
328 case '8':
329 case '9':
330 {
331 QByteArray ba;
332 ba += yyCh.toLatin1();
333 yyCh = getChar();
334 bool hex = yyCh == QLatin1Char('x');
335 if ( hex ) {
336 ba += yyCh.toLatin1();
337 yyCh = getChar();
338 }
339 while ( hex ? isxdigit(yyCh.toLatin1()) : yyCh.isDigit() ) {
340 ba += yyCh.toLatin1();
341 yyCh = getChar();
342 }
343 bool ok;
344 yyInteger = ba.toLongLong(&ok);
345 if (ok) return Tok_Integer;
346 break;
347 }
348 default:
349 yyCh = getChar();
350 }
351 }
352 }
353 return Tok_Eof;
354}
355
356static bool match( int t )
357{
358 bool matches = ( yyTok == t );
359 if ( matches )
360 yyTok = getToken();
361 return matches;
362}
363
364static bool matchString( QString &s )
365{
366 if ( yyTok != Tok_String )
367 return false;
368
369 s = yyString;
370 yyTok = getToken();
371 while ( yyTok == Tok_Plus ) {
372 yyTok = getToken();
373 if (yyTok == Tok_String)
374 s += yyString;
375 else {
376 yyMsg() << qPrintable(LU::tr(
377 "String used in translation can contain only literals"
378 " concatenated with other literals, not expressions or numbers.\n"));
379 return false;
380 }
381 yyTok = getToken();
382 }
383 return true;
384}
385
386static bool matchStringOrNull(QString &s)
387{
388 bool matches = matchString(s);
389 if (!matches) {
390 matches = (yyTok == Tok_null);
391 if (matches)
392 yyTok = getToken();
393 }
394 return matches;
395}
396
397/*
398 * match any expression that can return a number, which can be
399 * 1. Literal number (e.g. '11')
400 * 2. simple identifier (e.g. 'm_count')
401 * 3. simple function call (e.g. 'size()' )
402 * 4. function call on an object (e.g. 'list.size()')
403 * 5. function call on an object (e.g. 'list->size()')
404 *
405 * Other cases:
406 * size(2,4)
407 * list().size()
408 * list(a,b).size(2,4)
409 * etc...
410 */
411static bool matchExpression()
412{
413 if (match(Tok_Integer)) {
414 return true;
415 }
416
417 int parenlevel = 0;
418 while (match(Tok_Ident) || parenlevel > 0) {
419 if (yyTok == Tok_RightParen) {
420 if (parenlevel == 0) break;
421 --parenlevel;
422 yyTok = getToken();
423 } else if (yyTok == Tok_LeftParen) {
424 yyTok = getToken();
425 if (yyTok == Tok_RightParen) {
426 yyTok = getToken();
427 } else {
428 ++parenlevel;
429 }
430 } else if (yyTok == Tok_Ident) {
431 continue;
432 } else if (parenlevel == 0) {
433 return false;
434 }
435 }
436 return true;
437}
438
439static const QString context()
440{
441 QString context(yyPackage);
442 bool innerClass = false;
443 for (int i = 0; i < yyScope.size(); ++i) {
444 if (yyScope.at(i)->type == Scope::Clazz) {
445 if (innerClass)
446 context.append(QLatin1String("$"));
447 else
448 context.append(QLatin1String("."));
449
450 context.append(yyScope.at(i)->name);
451 innerClass = true;
452 }
453 }
454 return context.isEmpty() ? yyDefaultContext : context;
455}
456
457static void recordMessage(
458 Translator *tor, const QString &context, const QString &text, const QString &comment,
459 const QString &extracomment, bool plural)
460{
461 TranslatorMessage msg(
462 context, text, comment, QString(),
463 yyFileName, yyLineNo, QStringList(),
464 TranslatorMessage::Unfinished, plural);
465 msg.setExtraComment(extracomment.simplified());
466 tor->extend(msg);
467}
468
469static void parse( Translator *tor )
470{
471 QString text;
472 QString com;
473 QString extracomment;
474
475 yyCh = getChar();
476
477 yyTok = getToken();
478 while ( yyTok != Tok_Eof ) {
479 switch ( yyTok ) {
480 case Tok_class:
481 yyTok = getToken();
482 if(yyTok == Tok_Ident) {
483 yyScope.push(new Scope(yyIdent, Scope::Clazz, yyLineNo));
484 }
485 else {
486 yyMsg() << qPrintable(LU::tr("'class' must be followed by a class name.\n"));
487 break;
488 }
489 while (!match(Tok_LeftBrace)) {
490 yyTok = getToken();
491 }
492 break;
493
494 case Tok_tr:
495 yyTok = getToken();
496 if ( match(Tok_LeftParen) && matchString(text) ) {
497 com.clear();
498 bool plural = false;
499
500 if ( match(Tok_RightParen) ) {
501 // no comment
502 } else if (match(Tok_Comma) && matchStringOrNull(com)) { //comment
503 if ( match(Tok_RightParen)) {
504 // ok,
505 } else if (match(Tok_Comma)) {
506 plural = true;
507 }
508 }
509 if (!text.isEmpty())
510 recordMessage(tor, context(), text, com, extracomment, plural);
511 }
512 break;
513 case Tok_translate:
514 {
515 QString contextOverride;
516 yyTok = getToken();
517 if ( match(Tok_LeftParen) &&
518 matchString(contextOverride) &&
519 match(Tok_Comma) &&
520 matchString(text) ) {
521
522 com.clear();
523 bool plural = false;
524 if (!match(Tok_RightParen)) {
525 // look for comment
526 if ( match(Tok_Comma) && matchStringOrNull(com)) {
527 if (!match(Tok_RightParen)) {
528 if (match(Tok_Comma) && matchExpression() && match(Tok_RightParen)) {
529 plural = true;
530 } else {
531 break;
532 }
533 }
534 } else {
535 break;
536 }
537 }
538 if (!text.isEmpty())
539 recordMessage(tor, contextOverride, text, com, extracomment, plural);
540 }
541 }
542 break;
543
544 case Tok_Ident:
545 yyTok = getToken();
546 break;
547
548 case Tok_Comment:
549 if (yyComment.startsWith(QLatin1Char(':'))) {
550 yyComment.remove(0, 1);
551 extracomment.append(yyComment);
552 }
553 yyTok = getToken();
554 break;
555
556 case Tok_RightBrace:
557 if ( yyScope.isEmpty() ) {
558 yyMsg() << qPrintable(LU::tr("Excess closing brace.\n"));
559 }
560 else
561 delete (yyScope.pop());
562 extracomment.clear();
563 yyTok = getToken();
564 break;
565
566 case Tok_LeftBrace:
567 yyScope.push(new Scope(QString(), Scope::Other, yyLineNo));
568 yyTok = getToken();
569 break;
570
571 case Tok_Semicolon:
572 extracomment.clear();
573 yyTok = getToken();
574 break;
575
576 case Tok_Package:
577 yyTok = getToken();
578 while(!match(Tok_Semicolon)) {
579 switch(yyTok) {
580 case Tok_Ident:
581 yyPackage.append(yyIdent);
582 break;
583 case Tok_Dot:
584 yyPackage.append(QLatin1String("."));
585 break;
586 default:
587 yyMsg() << qPrintable(LU::tr("'package' must be followed by package name.\n"));
588 break;
589 }
590 yyTok = getToken();
591 }
592 break;
593
594 default:
595 yyTok = getToken();
596 }
597 }
598
599 if ( !yyScope.isEmpty() )
600 yyMsg(yyScope.top()->line) << qPrintable(LU::tr("Unbalanced opening brace.\n"));
601 else if ( yyParenDepth != 0 )
602 yyMsg(yyParenLineNo) << qPrintable(LU::tr("Unbalanced opening parenthesis.\n"));
603}
604
605
606bool loadJava(Translator &translator, const QString &filename, ConversionData &cd)
607{
608 QFile file(filename);
609 if (!file.open(QIODevice::ReadOnly)) {
610 cd.appendError(LU::tr("Cannot open %1: %2").arg(filename, file.errorString()));
611 return false;
612 }
613
614 yyDefaultContext = cd.m_defaultContext;
615 yyInPos = -1;
616 yyFileName = filename;
617 yyPackage.clear();
618 yyScope.clear();
619 yyTok = -1;
620 yyParenDepth = 0;
621 yyCurLineNo = 0;
622 yyParenLineNo = 1;
623
624 QTextStream ts(&file);
625 QByteArray codecName;
626 if (!cd.m_codecForSource.isEmpty())
627 codecName = cd.m_codecForSource;
628 else
629 codecName = translator.codecName(); // Just because it should be latin1 already
630 ts.setCodec(QTextCodec::codecForName(codecName));
631 ts.setAutoDetectUnicode(true);
632 yyInStr = ts.readAll();
633 yyInPos = 0;
634 yyFileName = filename;
635 yyCurLineNo = 1;
636 yyParenLineNo = 1;
637
638 parse(&translator);
639
640 // Java uses UTF-16 internally and Jambi makes UTF-8 for tr() purposes of it.
641 translator.setCodecName("UTF-8");
642 return true;
643}
644
645QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.