1 /**********************************************************************
2 ** Copyright (C) 2019 Riverbank Computing Limited
3 ** Copyright (C) 2002-2007 Detlev Offenbach <detlev@die-offenbachs.de>
4 **
5 ** This is a modified version of lupdate. The original is part of Qt-Linguist.
6 ** The copyright of the original file can be found below.
7 **
8 ** This version is modified to handle python sources.
9 **
10 ** The file is provided AS IS with NO WARRANTY OF ANY KIND,
11 ** INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR
12 ** A PARTICULAR PURPOSE.
13 ****************************************************************************/
14
15 /****************************************************************************
16 **
17 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
18 ** Contact: http://www.qt-project.org/legal
19 **
20 ** This file is part of the tools applications of the Qt Toolkit.
21 **
22 ** $QT_BEGIN_LICENSE:LGPL$
23 ** Commercial License Usage
24 ** Licensees holding valid commercial Qt licenses may use this file in
25 ** accordance with the commercial license agreement provided with the
26 ** Software or, alternatively, in accordance with the terms contained in
27 ** a written agreement between you and Digia. For licensing terms and
28 ** conditions see http://qt.digia.com/licensing. For further information
29 ** use the contact form at http://qt.digia.com/contact-us.
30 **
31 ** GNU Lesser General Public License Usage
32 ** Alternatively, this file may be used under the terms of the GNU Lesser
33 ** General Public License version 2.1 as published by the Free Software
34 ** Foundation and appearing in the file LICENSE.LGPL included in the
35 ** packaging of this file. Please review the following information to
36 ** ensure the GNU Lesser General Public License version 2.1 requirements
37 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
38 **
39 ** In addition, as a special exception, Digia gives you certain additional
40 ** rights. These rights are described in the Digia Qt LGPL Exception
41 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
42 **
43 ** GNU General Public License Usage
44 ** Alternatively, this file may be used under the terms of the GNU
45 ** General Public License version 3.0 as published by the Free Software
46 ** Foundation and appearing in the file LICENSE.GPL included in the
47 ** packaging of this file. Please review the following information to
48 ** ensure the GNU General Public License version 3.0 requirements will be
49 ** met: http://www.gnu.org/copyleft/gpl.html.
50 **
51 **
52 ** $QT_END_LICENSE$
53 **
54 ****************************************************************************/
55
56 #include <qfile.h>
57 #include <qregexp.h>
58 #include <qstring.h>
59 #include <qtextstream.h>
60 #include <qstack.h>
61
62 #include <ctype.h>
63 #include <errno.h>
64 #include <stdio.h>
65 #include <string.h>
66
67 #include <QTextCodec>
68 #include <QtXml>
69
70 #include "pylupdate.h"
71
72 static const char MagicComment[] = "TRANSLATOR ";
73
74 static QMap<QByteArray, int> needs_Q_OBJECT;
75 static QMap<QByteArray, int> lacks_Q_OBJECT;
76
77 /*
78 The first part of this source file is the Python tokenizer. We skip
79 most of Python; the only tokens that interest us are defined here.
80 */
81
82 enum { Tok_Eof, Tok_class, Tok_return, Tok_tr,
83 Tok_trUtf8, Tok_translate, Tok_Ident,
84 Tok_Comment, Tok_Dot, Tok_String,
85 Tok_LeftParen, Tok_RightParen,
86 Tok_Comma, Tok_None, Tok_Integer};
87
88 // The names of function aliases passed on the command line.
89 static const char *tr_function;
90 static const char *translate_function;
91
92 /*
93 The tokenizer maintains the following global variables. The names
94 should be self-explanatory.
95 */
96 static QByteArray yyFileName;
97 static int yyCh;
98 static char yyIdent[128];
99 static size_t yyIdentLen;
100 static char yyComment[65536];
101 static size_t yyCommentLen;
102 static char yyString[65536];
103 static size_t yyStringLen;
104 static qlonglong yyInteger;
105 static QStack<int> yySavedParenDepth;
106 static int yyParenDepth;
107 static int yyLineNo;
108 static int yyCurLineNo;
109 static int yyParenLineNo;
110 static QTextCodec *yyCodecForTr = 0;
111 static QTextCodec *yyCodecForSource = 0;
112
113 // the file to read from (if reading from a file)
114 static FILE *yyInFile;
115
116 // the string to read from and current position in the string (otherwise)
117 static QString yyInStr;
118 static int yyInPos;
119 // - 'rawbuf' is used to hold bytes before universal newline translation.
120 // - 'buf' is its higher-level counterpart, where every end-of-line appears as
121 // a single '\n' character, regardless of the end-of-line style used in input
122 // files.
123 static int buf, rawbuf;
124
125 static int (*getChar)();
126 static int (*peekChar)();
127
128 static bool yyParsingUtf8;
129
getTranslatedCharFromFile()130 static int getTranslatedCharFromFile()
131 {
132 int c;
133
134 if ( rawbuf < 0 ) // Empty raw buffer?
135 c = getc( yyInFile );
136 else {
137 c = rawbuf;
138 rawbuf = -1; // Declare the raw buffer empty.
139 }
140
141 // Universal newline translation, similar to what Python does
142 if ( c == '\r' ) {
143 c = getc( yyInFile ); // Last byte of a \r\n sequence?
144 if ( c != '\n')
145 {
146 rawbuf = c; // No, put it in 'rawbuf' for later processing.
147 // Logical character that will be seen by higher-level functions
148 c = '\n';
149 }
150 // In all cases, c == '\n' here.
151 }
152
153 return c;
154 }
155
getCharFromFile()156 static int getCharFromFile()
157 {
158 int c;
159
160 if (buf < 0 )
161 {
162 c = getTranslatedCharFromFile();
163
164 if (c == '\n') // This is after universal newline translation
165 yyCurLineNo++; // (i.e., a "logical" newline character).
166 }
167 else
168 {
169 c = buf;
170 buf = -1; // Declare the buffer empty.
171 }
172
173 return c;
174 }
175
peekCharFromFile()176 static int peekCharFromFile()
177 {
178 // Read a character, possibly performing universal newline translation,
179 // and put it in 'buf' so that the next call to getCharFromFile() finds it
180 // already available.
181 buf = getCharFromFile();
182 return buf;
183 }
184
startTokenizer(const char * fileName,int (* getCharFunc)(),int (* peekCharFunc)(),QTextCodec * codecForTr,QTextCodec * codecForSource)185 static void startTokenizer( const char *fileName, int (*getCharFunc)(),
186 int (*peekCharFunc)(), QTextCodec *codecForTr, QTextCodec *codecForSource )
187 {
188 yyInPos = 0;
189 buf = rawbuf = -1;
190 getChar = getCharFunc;
191 peekChar = peekCharFunc;
192
193 yyFileName = fileName;
194 yyCh = getChar();
195 yySavedParenDepth.clear();
196 yyParenDepth = 0;
197 yyCurLineNo = 1;
198 yyParenLineNo = 1;
199 yyCodecForTr = codecForTr;
200 if (!yyCodecForTr)
201 yyCodecForTr = QTextCodec::codecForName("ISO-8859-1");
202 Q_ASSERT(yyCodecForTr);
203 yyCodecForSource = codecForSource;
204
205 yyParsingUtf8 = false;
206 }
207
getToken()208 static int getToken()
209 {
210 const char tab[] = "abfnrtv";
211 const char backTab[] = "\a\b\f\n\r\t\v";
212 uint n;
213 bool quiet;
214
215 yyIdentLen = 0;
216 yyCommentLen = 0;
217 yyStringLen = 0;
218
219 while ( yyCh != EOF ) {
220 yyLineNo = yyCurLineNo;
221
222 if ( isalpha(yyCh) || yyCh == '_' ) {
223 do {
224 if ( yyIdentLen < sizeof(yyIdent) - 1 )
225 yyIdent[yyIdentLen++] = (char) yyCh;
226 yyCh = getChar();
227 } while ( isalnum(yyCh) || yyCh == '_' );
228 yyIdent[yyIdentLen] = '\0';
229
230 // Check for names passed on the command line.
231 if (tr_function && strcmp(yyIdent, tr_function) == 0)
232 return Tok_tr;
233
234 if (translate_function && strcmp(yyIdent, translate_function) == 0)
235 return Tok_translate;
236
237 bool might_be_str = false;
238
239 switch ( yyIdent[0] ) {
240 case 'N':
241 if ( strcmp(yyIdent + 1, "one") == 0 )
242 return Tok_None;
243 break;
244 case 'Q':
245 if (strcmp(yyIdent + 1, "T_TR_NOOP") == 0)
246 {
247 yyParsingUtf8 = false;
248 return Tok_tr;
249 }
250 else if (strcmp(yyIdent + 1, "T_TR_NOOP_UTF8") == 0)
251 {
252 yyParsingUtf8 = true;
253 return Tok_trUtf8;
254 }
255 else if (strcmp(yyIdent + 1, "T_TRANSLATE_NOOP") == 0)
256 {
257 yyParsingUtf8 = false;
258 return Tok_translate;
259 }
260 break;
261 case 'c':
262 if ( strcmp(yyIdent + 1, "lass") == 0 )
263 return Tok_class;
264 break;
265
266 case 'f':
267 /*
268 * QTranslator::findMessage() has the same parameters as
269 * QApplication::translate().
270 */
271 if ( strcmp(yyIdent + 1, "indMessage") == 0 )
272 return Tok_translate;
273
274 /* Drop through. */
275
276 case 'F':
277 if (yyIdent[1] == '\0')
278 might_be_str = true;
279 else if ((yyIdent[1] == 'r' || yyIdent[1] == 'R') && yyIdent[2] == '\0')
280 might_be_str = true;
281 break;
282
283 case 'r':
284 if ( strcmp(yyIdent + 1, "eturn") == 0 )
285 return Tok_return;
286
287 /* Drop through. */
288
289 case 'R':
290 if (yyIdent[1] == '\0')
291 might_be_str = true;
292 else if ((yyIdent[1] == 'f' || yyIdent[1] == 'F') && yyIdent[2] == '\0')
293 might_be_str = true;
294 else if ((yyIdent[1] == 'b' || yyIdent[1] == 'B') && yyIdent[2] == '\0')
295 might_be_str = true;
296 break;
297
298 case 'b':
299 case 'B':
300 if (yyIdent[1] == '\0')
301 might_be_str = true;
302 else if ((yyIdent[1] == 'r' || yyIdent[1] == 'R') && yyIdent[2] == '\0')
303 might_be_str = true;
304 break;
305
306 case 'u':
307 case 'U':
308 if (yyIdent[1] == '\0')
309 might_be_str = true;
310 break;
311
312 case 't':
313 if ( strcmp(yyIdent + 1, "r") == 0 ) {
314 yyParsingUtf8 = false;
315 return Tok_tr;
316 } else if ( qstrcmp(yyIdent + 1, "rUtf8") == 0 ) {
317 yyParsingUtf8 = true;
318 return Tok_trUtf8;
319 } else if ( qstrcmp(yyIdent + 1, "ranslate") == 0 ) {
320 yyParsingUtf8 = false;
321 return Tok_translate;
322 }
323 break;
324 case '_':
325 if ( strcmp(yyIdent + 1, "_tr") == 0 ) {
326 yyParsingUtf8 = false;
327 return Tok_tr;
328 } else if ( strcmp(yyIdent + 1, "_trUtf8") == 0 ) {
329 yyParsingUtf8 = true;
330 return Tok_trUtf8;
331 } else if ( qstrcmp(yyIdent + 1, "translate") == 0 ) {
332 yyParsingUtf8 = false;
333 return Tok_translate;
334 }
335 break;
336 }
337
338 /*
339 * Handle the standard Python v2 and v3 string prefixes by simply
340 * ignoring them.
341 */
342
343 if (!might_be_str)
344 return Tok_Ident;
345
346 if (yyCh != '"' && yyCh != '\'')
347 return Tok_Ident;
348 }
349 {
350 switch ( yyCh ) {
351 case '#':
352 do {
353 yyCh = getChar();
354 } while ( yyCh != EOF && yyCh != '\n' );
355 break;
356 case '"':
357 case '\'':
358 int quoteChar;
359 int trippelQuote, singleQuote;
360 int in;
361
362 quoteChar = yyCh;
363 trippelQuote = 0;
364 singleQuote = 1;
365 in = 0;
366 yyCh = getChar();
367 quiet = false;
368
369 while ( yyCh != EOF ) {
370 if ( singleQuote && (yyCh == '\n' || (in && yyCh == quoteChar)) )
371 break;
372
373 if ( yyCh == quoteChar ) {
374 if (peekChar() == quoteChar) {
375 yyCh = getChar();
376 if (!trippelQuote) {
377 trippelQuote = 1;
378 singleQuote = 0;
379 in = 1;
380 yyCh = getChar();
381 } else {
382 yyCh = getChar();
383 if (yyCh == quoteChar) {
384 trippelQuote = 0;
385 break;
386 }
387 }
388 } else if (trippelQuote) {
389 if ( yyStringLen < sizeof(yyString) - 1 )
390 yyString[yyStringLen++] = (char) yyCh;
391 yyCh = getChar();
392 continue;
393 } else
394 break;
395 } else
396 in = 1;
397
398 if ( yyCh == '\\' ) {
399 yyCh = getChar();
400
401 if ( yyCh == 'x' ) {
402 QByteArray hex = "0";
403
404 yyCh = getChar();
405 while ( isxdigit(yyCh) ) {
406 hex += (char) yyCh;
407 yyCh = getChar();
408 }
409 #if defined(_MSC_VER) && _MSC_VER >= 1400
410 sscanf_s( hex, "%x", &n );
411 #else
412 sscanf( hex, "%x", &n );
413 #endif
414 if ( yyStringLen < sizeof(yyString) - 1 )
415 yyString[yyStringLen++] = (char) n;
416 } else if ( yyCh >= '0' && yyCh < '8' ) {
417 QByteArray oct = "";
418 int n = 0;
419
420 do {
421 oct += (char) yyCh;
422 ++n;
423 yyCh = getChar();
424 } while ( yyCh >= '0' && yyCh < '8' && n < 3 );
425 #if defined(_MSC_VER) && _MSC_VER >= 1400
426 sscanf_s( oct, "%o", &n );
427 #else
428 sscanf( oct, "%o", &n );
429 #endif
430 if ( yyStringLen < sizeof(yyString) - 1 )
431 yyString[yyStringLen++] = (char) n;
432 } else if ( yyCh == '\n' ) {
433 yyCh = getChar();
434 } else {
435 const char *p = strchr( tab, yyCh );
436 if ( yyStringLen < sizeof(yyString) - 1 )
437 yyString[yyStringLen++] = ( p == 0 ) ?
438 (char) yyCh : backTab[p - tab];
439 yyCh = getChar();
440 }
441 } else {
442 if (!yyCodecForSource) {
443 if ( yyParsingUtf8 && yyCh >= 0x80 && !quiet) {
444 qWarning( "%s:%d: Non-ASCII character detected in trUtf8 string",
445 (const char *) yyFileName, yyLineNo );
446 quiet = true;
447 }
448 // common case: optimized
449 if ( yyStringLen < sizeof(yyString) - 1 )
450 yyString[yyStringLen++] = (char) yyCh;
451 yyCh = getChar();
452 } else {
453 QByteArray originalBytes;
454 while ( yyCh != EOF && (trippelQuote || yyCh != '\n') && yyCh != quoteChar && yyCh != '\\' ) {
455 if ( yyParsingUtf8 && yyCh >= 0x80 && !quiet) {
456 qWarning( "%s:%d: Non-ASCII character detected in trUtf8 string",
457 (const char *) yyFileName, yyLineNo );
458 quiet = true;
459 }
460 originalBytes += (char)yyCh;
461 yyCh = getChar();
462 }
463
464 QString unicodeStr = yyCodecForSource->toUnicode(originalBytes);
465 QByteArray convertedBytes;
466
467 if (!yyCodecForTr->canEncode(unicodeStr) && !quiet) {
468 qWarning( "%s:%d: Cannot convert Python string from %s to %s",
469 (const char *) yyFileName, yyLineNo, yyCodecForSource->name().constData(),
470 yyCodecForTr->name().constData() );
471 quiet = true;
472 }
473 convertedBytes = yyCodecForTr->fromUnicode(unicodeStr);
474
475 size_t len = qMin((size_t)convertedBytes.size(), sizeof(yyString) - yyStringLen - 1);
476 memcpy(yyString + yyStringLen, convertedBytes.constData(), len);
477 yyStringLen += len;
478 }
479 }
480 }
481 yyString[yyStringLen] = '\0';
482
483 if ( yyCh != quoteChar ) {
484 if (trippelQuote)
485 qWarning("%s:%d: Empty or unterminated triple quoted string",
486 (const char *)yyFileName, yyLineNo);
487 else
488 qWarning("%s:%d: Unterminated string",
489 (const char *)yyFileName, yyLineNo);
490 }
491
492 if ( yyCh == EOF ) {
493 return Tok_Eof;
494 } else {
495 yyCh = getChar();
496 return Tok_String;
497 }
498 break;
499 case '(':
500 if (yyParenDepth == 0)
501 yyParenLineNo = yyCurLineNo;
502 yyParenDepth++;
503 yyCh = getChar();
504 return Tok_LeftParen;
505 case ')':
506 if (yyParenDepth == 0)
507 yyParenLineNo = yyCurLineNo;
508 yyParenDepth--;
509 yyCh = getChar();
510 return Tok_RightParen;
511 case ',':
512 yyCh = getChar();
513 return Tok_Comma;
514 case '.':
515 yyCh = getChar();
516 return Tok_Dot;
517 case '0':
518 case '1':
519 case '2':
520 case '3':
521 case '4':
522 case '5':
523 case '6':
524 case '7':
525 case '8':
526 case '9':
527 {
528 QByteArray ba;
529 ba+=yyCh;
530 yyCh = getChar();
531 bool hex = yyCh == 'x';
532 if ( hex ) {
533 ba+=yyCh;
534 yyCh = getChar();
535 }
536 while ( (hex ? isxdigit(yyCh) : isdigit(yyCh)) ) {
537 ba+=yyCh;
538 yyCh = getChar();
539 }
540 bool ok;
541 yyInteger = ba.toLongLong(&ok);
542 if (ok) return Tok_Integer;
543 break;
544 }
545 default:
546 yyCh = getChar();
547 }
548 }
549 }
550 return Tok_Eof;
551 }
552
553 /*
554 The second part of this source file is the parser. It accomplishes
555 a very easy task: It finds all strings inside a tr() or translate()
556 call, and possibly finds out the context of the call. It supports
557 three cases:
558 (1) the context is specified, as in FunnyDialog.tr("Hello") or
559 translate("FunnyDialog", "Hello");
560 (2) the call appears within an inlined function;
561 (3) the call appears within a function defined outside the class definition.
562 */
563
564 static int yyTok;
565
match(int t)566 static bool match( int t )
567 {
568 bool matches = ( yyTok == t );
569 if ( matches )
570 yyTok = getToken();
571 return matches;
572 }
573
matchString(QByteArray * s)574 static bool matchString( QByteArray *s )
575 {
576 bool matches = ( yyTok == Tok_String );
577 *s = "";
578 while ( yyTok == Tok_String ) {
579 *s += yyString;
580 yyTok = getToken();
581 }
582 return matches;
583 }
584
matchStringOrNone(QByteArray * s)585 static bool matchStringOrNone(QByteArray *s)
586 {
587 bool matches = matchString(s);
588
589 if (!matches)
590 matches = match(Tok_None);
591
592 return matches;
593 }
594
595 /*
596 * Match any expression that can return a number. It may match invalid code
597 * but it shouldn't fail to match valid code.
598 */
matchExpression()599 static bool matchExpression()
600 {
601 bool matches = false;
602
603 for (;;)
604 {
605 if (match(Tok_Integer))
606 {
607 matches = true;
608 }
609 else if (match(Tok_Ident))
610 {
611 matches = true;
612 }
613 else if (match(Tok_LeftParen))
614 {
615 int paren_level = 1;
616
617 matches = false;
618
619 while (!match(Tok_Eof))
620 {
621 if (match(Tok_LeftParen))
622 {
623 ++paren_level;
624 }
625 else if (match(Tok_RightParen))
626 {
627 if (--paren_level == 0)
628 {
629 matches = true;
630 break;
631 }
632 }
633 else
634 {
635 yyTok = getToken();
636 }
637 }
638 }
639 else
640 {
641 break;
642 }
643 }
644
645 return matches;
646 }
647
parse(MetaTranslator * tor,const char * initialContext,const char * defaultContext)648 static void parse( MetaTranslator *tor, const char *initialContext,
649 const char *defaultContext )
650 {
651 QMap<QByteArray, QByteArray> qualifiedContexts;
652 QByteArray context;
653 QByteArray text;
654 QByteArray com;
655 QByteArray functionContext = initialContext;
656 QByteArray prefix;
657 bool utf8 = false;
658
659 yyTok = getToken();
660 while ( yyTok != Tok_Eof ) {
661 switch ( yyTok ) {
662 case Tok_class:
663 yyTok = getToken();
664 functionContext = yyIdent;
665 yyTok = getToken();
666 break;
667 case Tok_tr:
668 case Tok_trUtf8:
669 utf8 = (yyTok == Tok_trUtf8 || (yyCodecForTr && strcmp(yyCodecForTr->name(), "UTF-8") == 0));
670 yyTok = getToken();
671 if (match(Tok_LeftParen) && matchString(&text))
672 {
673 com = "";
674 bool plural = false;
675
676 // Note that this isn't as rigorous as the parsing of
677 // translate() below.
678 if (match(Tok_RightParen))
679 {
680 // There is no comment or plural arguments.
681 }
682 else if (match(Tok_Comma) && matchStringOrNone(&com))
683 {
684 // There is a comment argument.
685 if (match(Tok_RightParen))
686 {
687 // There is no plural argument.
688 }
689 else if (match(Tok_Comma))
690 {
691 // There is a plural argument.
692 plural = true;
693 }
694 }
695
696 if (prefix.isNull())
697 context = defaultContext;
698 else if (qstrcmp(prefix, "self") == 0)
699 context = functionContext;
700 else
701 context = prefix;
702
703 prefix = (const char *) 0;
704
705 if (qualifiedContexts.contains(context))
706 context = qualifiedContexts[context];
707
708 if (!text.isEmpty())
709 {
710 tor->insert(MetaTranslatorMessage(context, text, com,
711 yyFileName, yyParenLineNo,
712 QStringList(), utf8,
713 MetaTranslatorMessage::Unfinished, plural));
714 }
715 }
716 break;
717 case Tok_translate:
718 yyTok = getToken();
719 if ( match(Tok_LeftParen) &&
720 matchString(&context) &&
721 match(Tok_Comma) &&
722 matchString(&text) ) {
723 com = "";
724 bool plural = false;
725 if (!match(Tok_RightParen)) {
726 // look for comment
727 if ( match(Tok_Comma) && matchStringOrNone(&com)) {
728 if (!match(Tok_RightParen)) {
729 // Look for n.
730 if (match(Tok_Comma)) {
731 if (matchExpression() && match(Tok_RightParen)) {
732 plural = true;
733 } else {
734 break;
735 }
736 } else {
737 break;
738 }
739 }
740 } else {
741 break;
742 }
743 }
744 if (!text.isEmpty())
745 {
746 tor->insert( MetaTranslatorMessage(context, text, com,
747 yyFileName, yyParenLineNo,
748 QStringList(), true,
749 MetaTranslatorMessage::Unfinished,
750 plural) );
751 }
752 }
753 break;
754 case Tok_Ident:
755 if ( !prefix.isNull() )
756 prefix += ".";
757 prefix += yyIdent;
758 yyTok = getToken();
759 if ( yyTok != Tok_Dot )
760 prefix = (const char *) 0;
761 break;
762 case Tok_Comment:
763 com = yyComment;
764 com = com.simplified();
765 if ( com.left(sizeof(MagicComment) - 1) == MagicComment ) {
766 com.remove( 0, sizeof(MagicComment) - 1 );
767 int k = com.indexOf( ' ' );
768 if ( k == -1 ) {
769 context = com;
770 } else {
771 context = com.left( k );
772 com.remove( 0, k + 1 );
773 tor->insert( MetaTranslatorMessage(context, "", com,
774 yyFileName, yyParenLineNo,
775 QStringList(), false) );
776
777 }
778 }
779 yyTok = getToken();
780 break;
781 default:
782 yyTok = getToken();
783 }
784 }
785
786 if ( yyParenDepth != 0 )
787 qWarning( "%s: Unbalanced parentheses in Python code",
788 (const char *) yyFileName );
789 }
790
fetchtr_py(const char * fileName,MetaTranslator * tor,const char * defaultContext,bool mustExist,const char * codecForSource,const char * tr_func,const char * translate_func)791 void fetchtr_py(const char *fileName, MetaTranslator *tor,
792 const char *defaultContext, bool mustExist,
793 const char *codecForSource, const char *tr_func,
794 const char *translate_func)
795 {
796 tr_function = tr_func;
797 translate_function = translate_func;
798
799 #if defined(_MSC_VER) && _MSC_VER >= 1400
800 if (fopen_s(&yyInFile, fileName, "r")) {
801 if ( mustExist ) {
802 char buf[100];
803 strerror_s(buf, sizeof(buf), errno);
804 fprintf( stderr,
805 "pylupdate5 error: Cannot open Python source file '%s': %s\n",
806 fileName, buf );
807 }
808 #else
809 yyInFile = fopen( fileName, "r" );
810 if ( yyInFile == 0 ) {
811 if ( mustExist )
812 fprintf( stderr,
813 "pylupdate5 error: Cannot open Python source file '%s': %s\n",
814 fileName, strerror(errno) );
815 #endif
816 return;
817 }
818
819 startTokenizer( fileName, getCharFromFile, peekCharFromFile, tor->codecForTr(), QTextCodec::codecForName(codecForSource) );
820 parse( tor, 0, defaultContext );
821 fclose( yyInFile );
822 }
823
824 class UiHandler : public QXmlDefaultHandler
825 {
826 public:
827 UiHandler( MetaTranslator *translator, const char *fileName )
828 : tor( translator ), fname( fileName ), comment( "" ) { }
829
830 virtual bool startElement( const QString& namespaceURI,
831 const QString& localName, const QString& qName,
832 const QXmlAttributes& atts );
833 virtual bool endElement( const QString& namespaceURI,
834 const QString& localName, const QString& qName );
835 virtual bool characters( const QString& ch );
836 virtual bool fatalError( const QXmlParseException& exception );
837
838 virtual void setDocumentLocator(QXmlLocator *locator)
839 {
840 m_locator = locator;
841 }
842 QXmlLocator *m_locator;
843 private:
844 void flush();
845
846 MetaTranslator *tor;
847 QByteArray fname;
848 QString context;
849 QString source;
850 QString comment;
851
852 QString accum;
853 int m_lineNumber;
854 bool trString;
855 };
856
857 bool UiHandler::startElement( const QString& /* namespaceURI */,
858 const QString& /* localName */,
859 const QString& qName,
860 const QXmlAttributes& atts )
861 {
862 if ( qName == QString("item") ) {
863 flush();
864 if ( !atts.value(QString("text")).isEmpty() )
865 source = atts.value( QString("text") );
866 } else if ( qName == QString("string") ) {
867 flush();
868 if (atts.value(QString("notr")).isEmpty() ||
869 atts.value(QString("notr")) != QString("true")) {
870 trString = true;
871 comment = atts.value(QString("comment"));
872 } else {
873 trString = false;
874 }
875 }
876 if (trString) m_lineNumber = m_locator->lineNumber();
877 accum.truncate( 0 );
878 return true;
879 }
880
881 bool UiHandler::endElement( const QString& /* namespaceURI */,
882 const QString& /* localName */,
883 const QString& qName )
884 {
885 accum.replace( QRegExp(QString("\r\n")), "\n" );
886
887 if ( qName == QString("class") ) {
888 if ( context.isEmpty() )
889 context = accum;
890 } else if ( qName == QString("string") && trString ) {
891 source = accum;
892 } else if ( qName == QString("comment") ) {
893 comment = accum;
894 flush();
895 } else {
896 flush();
897 }
898 return true;
899 }
900
901 bool UiHandler::characters( const QString& ch )
902 {
903 accum += ch;
904 return true;
905 }
906
907 bool UiHandler::fatalError( const QXmlParseException& exception )
908 {
909 QString msg;
910 msg.sprintf( "Parse error at line %d, column %d (%s).",
911 exception.lineNumber(), exception.columnNumber(),
912 exception.message().toLatin1().data() );
913 fprintf( stderr, "XML error: %s\n", msg.toLatin1().data() );
914 return false;
915 }
916
917 void UiHandler::flush()
918 {
919 if ( !context.isEmpty() && !source.isEmpty() )
920 tor->insert( MetaTranslatorMessage(context.toUtf8(), source.toUtf8(),
921 comment.toUtf8(), QString(fname), m_lineNumber,
922 QStringList(), true) );
923 source.truncate( 0 );
924 comment.truncate( 0 );
925 }
926
927 void fetchtr_ui( const char *fileName, MetaTranslator *tor,
928 const char * /* defaultContext */, bool mustExist )
929 {
930 QFile f( fileName );
931 if ( !f.open(QIODevice::ReadOnly) ) {
932 if ( mustExist ) {
933 #if defined(_MSC_VER) && _MSC_VER >= 1400
934 char buf[100];
935 strerror_s(buf, sizeof(buf), errno);
936 fprintf( stderr, "pylupdate5 error: cannot open UI file '%s': %s\n",
937 fileName, buf );
938 #else
939 fprintf( stderr, "pylupdate5 error: cannot open UI file '%s': %s\n",
940 fileName, strerror(errno) );
941 #endif
942 }
943 return;
944 }
945
946 QXmlInputSource in( &f );
947 QXmlSimpleReader reader;
948 reader.setFeature( "http://xml.org/sax/features/namespaces", false );
949 reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", true );
950 reader.setFeature( "http://trolltech.com/xml/features/report-whitespace"
951 "-only-CharData", false );
952 QXmlDefaultHandler *hand = new UiHandler( tor, fileName );
953 reader.setContentHandler( hand );
954 reader.setErrorHandler( hand );
955
956 if ( !reader.parse(in) )
957 fprintf( stderr, "%s: Parse error in UI file\n", fileName );
958 reader.setContentHandler( 0 );
959 reader.setErrorHandler( 0 );
960 delete hand;
961 f.close();
962 }
963