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