1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Linguist of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "translator.h"
43 
44 #include <QtCore/QByteArray>
45 #include <QtCore/QDebug>
46 #include <QtCore/QTextCodec>
47 #include <QtCore/QTextStream>
48 
49 #include <QtXml/QXmlStreamReader>
50 #include <QtXml/QXmlStreamAttribute>
51 
52 #define STRINGIFY_INTERNAL(x) #x
53 #define STRINGIFY(x) STRINGIFY_INTERNAL(x)
54 #define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s)))
55 
56 QT_BEGIN_NAMESPACE
57 
58 /*
59  * The encodings are a total mess.
60  * A Translator has a codecForTr(). Each message's text will be passed to tr()
61  * in that encoding or as UTF-8 to trUtf8() if it is flagged as such.
62  * For ts 2.0, the file content is always uniformly in UTF-8. The file stores
63  * the codecForTr default and marks deviating messages accordingly.
64  * For ts 1.1, the file content is in mixed encoding. Each message is encoded
65  * the way it will be passed to tr() (with 8-bit characters encoded as numeric
66  * entities) or trUtf8(). The file stores the encoding and codecForTr in one
67  * attribute, for both the default and each deviating message.
68  */
69 
70 
operator <<(QDebug & d,const QXmlStreamAttribute & attr)71 QDebug &operator<<(QDebug &d, const QXmlStreamAttribute &attr)
72 {
73     return d << "[" << attr.name().toString() << "," << attr.value().toString() << "]";
74 }
75 
76 
77 class TSReader : public QXmlStreamReader
78 {
79 public:
TSReader(QIODevice & dev,ConversionData & cd)80     TSReader(QIODevice &dev, ConversionData &cd)
81       : QXmlStreamReader(&dev), m_cd(cd)
82     {}
83 
84     // the "real thing"
85     bool read(Translator &translator);
86 
87 private:
elementStarts(const QString & str) const88     bool elementStarts(const QString &str) const
89     {
90         return isStartElement() && name() == str;
91     }
92 
isWhiteSpace() const93     bool isWhiteSpace() const
94     {
95         return isCharacters() && text().toString().trimmed().isEmpty();
96     }
97 
98     // needed to expand <byte ... />
99     QString readContents();
100     // needed to join <lengthvariant>s
101     QString readTransContents();
102 
103     void handleError();
104 
105     ConversionData &m_cd;
106 };
107 
handleError()108 void TSReader::handleError()
109 {
110     if (isComment())
111         return;
112     if (hasError() && error() == CustomError) // raised by readContents
113         return;
114 
115     const QString loc = QString::fromLatin1("at %3:%1:%2")
116         .arg(lineNumber()).arg(columnNumber()).arg(m_cd.m_sourceFileName);
117 
118     switch (tokenType()) {
119     case NoToken: // Cannot happen
120     default: // likewise
121     case Invalid:
122         raiseError(QString::fromLatin1("Parse error %1: %2").arg(loc, errorString()));
123         break;
124     case StartElement:
125         raiseError(QString::fromLatin1("Unexpected tag <%1> %2").arg(name().toString(), loc));
126         break;
127     case Characters:
128         {
129             QString tok = text().toString();
130             if (tok.length() > 30)
131                 tok = tok.left(30) + QLatin1String("[...]");
132             raiseError(QString::fromLatin1("Unexpected characters '%1' %2").arg(tok, loc));
133         }
134         break;
135     case EntityReference:
136         raiseError(QString::fromLatin1("Unexpected entity '&%1;' %2").arg(name().toString(), loc));
137         break;
138     case ProcessingInstruction:
139         raiseError(QString::fromLatin1("Unexpected processing instruction %1").arg(loc));
140         break;
141     }
142 }
143 
byteValue(QString value)144 static QString byteValue(QString value)
145 {
146     int base = 10;
147     if (value.startsWith(QLatin1String("x"))) {
148         base = 16;
149         value.remove(0, 1);
150     }
151     int n = value.toUInt(0, base);
152     return (n != 0) ? QString(QChar(n)) : QString();
153 }
154 
readContents()155 QString TSReader::readContents()
156 {
157     STRING(byte);
158     STRING(value);
159 
160     QString result;
161     while (!atEnd()) {
162         readNext();
163         if (isEndElement()) {
164             break;
165         } else if (isCharacters()) {
166             result += text();
167         } else if (elementStarts(strbyte)) {
168             // <byte value="...">
169             result += byteValue(attributes().value(strvalue).toString());
170             readNext();
171             if (!isEndElement()) {
172                 handleError();
173                 break;
174             }
175         } else {
176             handleError();
177             break;
178         }
179     }
180     //qDebug() << "TEXT: " << result;
181     return result;
182 }
183 
readTransContents()184 QString TSReader::readTransContents()
185 {
186     STRING(lengthvariant);
187     STRING(variants);
188     STRING(yes);
189 
190     if (attributes().value(strvariants) == stryes) {
191         QString result;
192         while (!atEnd()) {
193             readNext();
194             if (isEndElement()) {
195                 break;
196             } else if (isWhiteSpace()) {
197                 // ignore these, just whitespace
198             } else if (elementStarts(strlengthvariant)) {
199                 if (!result.isEmpty())
200                     result += QChar(Translator::BinaryVariantSeparator);
201                 result += readContents();
202             } else {
203                 handleError();
204                 break;
205             }
206         }
207         return result;
208     } else {
209         return readContents();
210     }
211 }
212 
read(Translator & translator)213 bool TSReader::read(Translator &translator)
214 {
215     STRING(both);
216     STRING(byte);
217     STRING(comment);
218     STRING(context);
219     STRING(defaultcodec);
220     STRING(encoding);
221     STRING(extracomment);
222     STRING(filename);
223     STRING(id);
224     STRING(language);
225     STRING(line);
226     STRING(location);
227     STRING(message);
228     STRING(name);
229     STRING(numerus);
230     STRING(numerusform);
231     STRING(obsolete);
232     STRING(oldcomment);
233     STRING(oldsource);
234     STRING(source);
235     STRING(sourcelanguage);
236     STRING(translation);
237     STRING(translatorcomment);
238     STRING(true);
239     STRING(TS);
240     STRING(type);
241     STRING(unfinished);
242     STRING(userdata);
243     STRING(utf8);
244     STRING(value);
245     //STRING(version);
246     STRING(yes);
247 
248     static const QString strextrans(QLatin1String("extra-"));
249     static const QString strUtf8(QLatin1String("UTF-8"));
250 
251     while (!atEnd()) {
252         readNext();
253         if (isStartDocument()) {
254             // <!DOCTYPE TS>
255             //qDebug() << attributes();
256         } else if (isEndDocument()) {
257             // <!DOCTYPE TS>
258             //qDebug() << attributes();
259         } else if (isDTD()) {
260             // <!DOCTYPE TS>
261             //qDebug() << tokenString();
262         } else if (elementStarts(strTS)) {
263             // <TS>
264             //qDebug() << "TS " << attributes();
265             QHash<QString, int> currentLine;
266             QString currentFile;
267             bool maybeRelative = false, maybeAbsolute = false;
268 
269             QXmlStreamAttributes atts = attributes();
270             //QString version = atts.value(strversion).toString();
271             translator.setLanguageCode(atts.value(strlanguage).toString());
272             translator.setSourceLanguageCode(atts.value(strsourcelanguage).toString());
273             while (!atEnd()) {
274                 readNext();
275                 if (isEndElement()) {
276                     // </TS> found, finish local loop
277                     break;
278                 } else if (isWhiteSpace()) {
279                     // ignore these, just whitespace
280                 } else if (elementStarts(strdefaultcodec)) {
281                     // <defaultcodec>
282                     const QString &codec = readElementText();
283                     if (!codec.isEmpty())
284                         translator.setCodecName(codec.toLatin1());
285                     // </defaultcodec>
286                 } else if (isStartElement()
287                         && name().toString().startsWith(strextrans)) {
288                     // <extra-...>
289                     QString tag = name().toString();
290                     translator.setExtra(tag.mid(6), readContents());
291                     // </extra-...>
292                 } else if (elementStarts(strcontext)) {
293                     // <context>
294                     QString context;
295                     while (!atEnd()) {
296                         readNext();
297                         if (isEndElement()) {
298                             // </context> found, finish local loop
299                             break;
300                         } else if (isWhiteSpace()) {
301                             // ignore these, just whitespace
302                         } else if (elementStarts(strname)) {
303                             // <name>
304                             context = readElementText();
305                             // </name>
306                         } else if (elementStarts(strmessage)) {
307                             // <message>
308                             TranslatorMessage::References refs;
309                             QString currentMsgFile = currentFile;
310 
311                             TranslatorMessage msg;
312                             msg.setId(attributes().value(strid).toString());
313                             msg.setContext(context);
314                             msg.setType(TranslatorMessage::Finished);
315                             msg.setPlural(attributes().value(strnumerus) == stryes);
316                             const QStringRef &utf8Attr = attributes().value(strutf8);
317                             msg.setNonUtf8(utf8Attr == strboth);
318                             msg.setUtf8(msg.isNonUtf8() || utf8Attr == strtrue
319                                  ||  attributes().value(strencoding) == strUtf8);
320                             while (!atEnd()) {
321                                 readNext();
322                                 if (isEndElement()) {
323                                     // </message> found, finish local loop
324                                     msg.setReferences(refs);
325                                     translator.append(msg);
326                                     break;
327                                 } else if (isWhiteSpace()) {
328                                     // ignore these, just whitespace
329                                 } else if (elementStarts(strsource)) {
330                                     // <source>...</source>
331                                     msg.setSourceText(readContents());
332                                 } else if (elementStarts(stroldsource)) {
333                                     // <oldsource>...</oldsource>
334                                     msg.setOldSourceText(readContents());
335                                 } else if (elementStarts(stroldcomment)) {
336                                     // <oldcomment>...</oldcomment>
337                                     msg.setOldComment(readContents());
338                                 } else if (elementStarts(strextracomment)) {
339                                     // <extracomment>...</extracomment>
340                                     msg.setExtraComment(readContents());
341                                 } else if (elementStarts(strtranslatorcomment)) {
342                                     // <translatorcomment>...</translatorcomment>
343                                     msg.setTranslatorComment(readContents());
344                                 } else if (elementStarts(strlocation)) {
345                                     // <location/>
346                                     maybeAbsolute = true;
347                                     QXmlStreamAttributes atts = attributes();
348                                     QString fileName = atts.value(strfilename).toString();
349                                     if (fileName.isEmpty()) {
350                                         fileName = currentMsgFile;
351                                         maybeRelative = true;
352                                     } else {
353                                         if (refs.isEmpty())
354                                             currentFile = fileName;
355                                         currentMsgFile = fileName;
356                                     }
357                                     const QString lin = atts.value(strline).toString();
358                                     if (lin.isEmpty()) {
359                                         refs.append(TranslatorMessage::Reference(fileName, -1));
360                                     } else {
361                                         bool bOK;
362                                         int lineNo = lin.toInt(&bOK);
363                                         if (bOK) {
364                                             if (lin.startsWith(QLatin1Char('+')) || lin.startsWith(QLatin1Char('-'))) {
365                                                 lineNo = (currentLine[fileName] += lineNo);
366                                                 maybeRelative = true;
367                                             }
368                                             refs.append(TranslatorMessage::Reference(fileName, lineNo));
369                                         }
370                                     }
371                                     readContents();
372                                 } else if (elementStarts(strcomment)) {
373                                     // <comment>...</comment>
374                                     msg.setComment(readContents());
375                                 } else if (elementStarts(struserdata)) {
376                                     // <userdata>...</userdata>
377                                     msg.setUserData(readContents());
378                                 } else if (elementStarts(strtranslation)) {
379                                     // <translation>
380                                     QXmlStreamAttributes atts = attributes();
381                                     QStringRef type = atts.value(strtype);
382                                     if (type == strunfinished)
383                                         msg.setType(TranslatorMessage::Unfinished);
384                                     else if (type == strobsolete)
385                                         msg.setType(TranslatorMessage::Obsolete);
386                                     if (msg.isPlural()) {
387                                         QStringList translations;
388                                         while (!atEnd()) {
389                                             readNext();
390                                             if (isEndElement()) {
391                                                 break;
392                                             } else if (isWhiteSpace()) {
393                                                 // ignore these, just whitespace
394                                             } else if (elementStarts(strnumerusform)) {
395                                                 translations.append(readTransContents());
396                                             } else {
397                                                 handleError();
398                                                 break;
399                                             }
400                                         }
401                                         msg.setTranslations(translations);
402                                     } else {
403                                         msg.setTranslation(readTransContents());
404                                     }
405                                     // </translation>
406                                 } else if (isStartElement()
407                                         && name().toString().startsWith(strextrans)) {
408                                     // <extra-...>
409                                     QString tag = name().toString();
410                                     msg.setExtra(tag.mid(6), readContents());
411                                     // </extra-...>
412                                 } else {
413                                     handleError();
414                                 }
415                             }
416                             // </message>
417                         } else {
418                             handleError();
419                         }
420                     }
421                     // </context>
422                 } else {
423                     handleError();
424                 }
425                 translator.setLocationsType(maybeRelative ? Translator::RelativeLocations :
426                                             maybeAbsolute ? Translator::AbsoluteLocations :
427                                                             Translator::NoLocations);
428             } // </TS>
429         } else {
430             handleError();
431         }
432     }
433     if (hasError()) {
434         m_cd.appendError(errorString());
435         return false;
436     }
437     return true;
438 }
439 
numericEntity(int ch)440 static QString numericEntity(int ch)
441 {
442     return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>")
443         : QLatin1String("&#x%1;")) .arg(ch, 0, 16);
444 }
445 
protect(const QString & str)446 static QString protect(const QString &str)
447 {
448     QString result;
449     result.reserve(str.length() * 12 / 10);
450     for (int i = 0; i != str.size(); ++i) {
451         uint c = str.at(i).unicode();
452         switch (c) {
453         case '\"':
454             result += QLatin1String("&quot;");
455             break;
456         case '&':
457             result += QLatin1String("&amp;");
458             break;
459         case '>':
460             result += QLatin1String("&gt;");
461             break;
462         case '<':
463             result += QLatin1String("&lt;");
464             break;
465         case '\'':
466             result += QLatin1String("&apos;");
467             break;
468         default:
469             if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
470                 result += numericEntity(c);
471             else // this also covers surrogates
472                 result += QChar(c);
473         }
474     }
475     return result;
476 }
477 
evilBytes(const QString & str,bool isUtf8,int format,const QByteArray & codecName)478 static QString evilBytes(const QString& str,
479     bool isUtf8, int format, const QByteArray &codecName)
480 {
481     //qDebug() << "EVIL: " << str << isUtf8 << format << codecName;
482     if (isUtf8)
483         return protect(str);
484     if (format == 20)
485         return protect(str);
486     if (codecName == "UTF-8")
487         return protect(str);
488     QTextCodec *codec = QTextCodec::codecForName(codecName);
489     if (!codec)
490         return protect(str);
491     QString t = QString::fromLatin1(codec->fromUnicode(protect(str)).data());
492     int len = (int) t.length();
493     QString result;
494     // FIXME: Factor is sensible only for latin scripts, probably.
495     result.reserve(t.length() * 2);
496     for (int k = 0; k < len; k++) {
497         if (t[k].unicode() >= 0x7f)
498             result += numericEntity(t[k].unicode());
499         else
500             result += t[k];
501     }
502     return result;
503 }
504 
writeExtras(QTextStream & t,const char * indent,const TranslatorMessage::ExtraData & extras,const QRegExp & drops)505 static void writeExtras(QTextStream &t, const char *indent,
506                         const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
507 {
508     for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
509         if (!drops.exactMatch(it.key())) {
510             t << indent << "<extra-" << it.key() << '>'
511               << protect(it.value())
512               << "</extra-" << it.key() << ">\n";
513         }
514     }
515 }
516 
writeVariants(QTextStream & t,const char * indent,const QString & input)517 static void writeVariants(QTextStream &t, const char *indent, const QString &input)
518 {
519     int offset;
520     if ((offset = input.indexOf(QChar(Translator::BinaryVariantSeparator))) >= 0) {
521         t << " variants=\"yes\">";
522         int start = 0;
523         forever {
524             t << "\n    " << indent << "<lengthvariant>"
525               << protect(input.mid(start, offset - start))
526               << "</lengthvariant>";
527             if (offset == input.length())
528                 break;
529             start = offset + 1;
530             offset = input.indexOf(QChar(Translator::BinaryVariantSeparator), start);
531             if (offset < 0)
532                 offset = input.length();
533         }
534         t << "\n" << indent;
535     } else {
536         t << ">" << protect(input);
537     }
538 }
539 
saveTS(const Translator & translator,QIODevice & dev,ConversionData & cd,int format)540 bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd, int format)
541 {
542     bool result = true;
543     QTextStream t(&dev);
544     t.setCodec(QTextCodec::codecForName("UTF-8"));
545     bool trIsUtf8 = (translator.codecName() == "UTF-8");
546     //qDebug() << translator.codecName();
547     bool fileIsUtf8 = (format == 20 || trIsUtf8);
548 
549     // The xml prolog allows processors to easily detect the correct encoding
550     t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n";
551 
552     if (format == 11)
553         t << "<TS version=\"1.1\"";
554     else
555         t << "<TS version=\"2.0\"";
556 
557     QString languageCode = translator.languageCode();
558     if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
559         t << " language=\"" << languageCode << "\"";
560     if (format == 20) {
561         languageCode = translator.sourceLanguageCode();
562         if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
563             t << " sourcelanguage=\"" << languageCode << "\"";
564     }
565     t << ">\n";
566 
567     QByteArray codecName = translator.codecName();
568     if (codecName != "ISO-8859-1")
569         t << "<defaultcodec>" << codecName << "</defaultcodec>\n";
570 
571     QRegExp drops(cd.dropTags().join(QLatin1String("|")));
572 
573     if (format == 20)
574         writeExtras(t, "    ", translator.extras(), drops);
575 
576     QHash<QString, QList<TranslatorMessage> > messageOrder;
577     QList<QString> contextOrder;
578     foreach (const TranslatorMessage &msg, translator.messages()) {
579         // no need for such noise
580         if (msg.type() == TranslatorMessage::Obsolete && msg.translation().isEmpty())
581             continue;
582 
583         QList<TranslatorMessage> &context = messageOrder[msg.context()];
584         if (context.isEmpty())
585             contextOrder.append(msg.context());
586         context.append(msg);
587     }
588     if (cd.sortContexts())
589         qSort(contextOrder);
590 
591     QHash<QString, int> currentLine;
592     QString currentFile;
593     foreach (const QString &context, contextOrder) {
594         const TranslatorMessage &firstMsg = messageOrder[context].first();
595         t << "<context" << ((!fileIsUtf8 && firstMsg.isUtf8()) ? " encoding=\"UTF-8\"" : "") << ">\n";
596 
597         t << "    <name>"
598           << evilBytes(context, firstMsg.isUtf8() || fileIsUtf8, format, codecName)
599           << "</name>\n";
600         foreach (const TranslatorMessage &msg, messageOrder[context]) {
601             //msg.dump();
602 
603             bool isUtf8 = msg.isUtf8();
604             bool second = false;
605             forever {
606 
607                 t << "    <message";
608                 if (!msg.id().isEmpty())
609                     t << " id=\"" << msg.id() << "\"";
610                 if (!trIsUtf8) {
611                     if (format == 11) {
612                         if (isUtf8)
613                             t << " encoding=\"UTF-8\"";
614                     } else {
615                         if (msg.isUtf8()) {
616                             if (msg.isNonUtf8())
617                                 t << " utf8=\"both\"";
618                             else
619                                 t << " utf8=\"true\"";
620                         }
621                     }
622                 }
623                 if (msg.isPlural())
624                     t << " numerus=\"yes\"";
625                 t << ">\n";
626                 if (translator.locationsType() != Translator::NoLocations) {
627                     QString cfile = currentFile;
628                     bool first = true;
629                     foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) {
630                         QString fn = cd.m_targetDir.relativeFilePath(ref.fileName())
631                                     .replace(QLatin1Char('\\'),QLatin1Char('/'));
632                         int ln = ref.lineNumber();
633                         QString ld;
634                         if (translator.locationsType() == Translator::RelativeLocations) {
635                             if (ln != -1) {
636                                 int dlt = ln - currentLine[fn];
637                                 if (dlt >= 0)
638                                     ld.append(QLatin1Char('+'));
639                                 ld.append(QString::number(dlt));
640                                 currentLine[fn] = ln;
641                             }
642 
643                             if (fn != cfile) {
644                                 if (first)
645                                     currentFile = fn;
646                                 cfile = fn;
647                             } else {
648                                 fn.clear();
649                             }
650                             first = false;
651                         } else {
652                             if (ln != -1)
653                                 ld = QString::number(ln);
654                         }
655                         t << "        <location";
656                         if (!fn.isEmpty())
657                             t << " filename=\"" << fn << "\"";
658                         if (!ld.isEmpty())
659                             t << " line=\"" << ld << "\"";
660                         t << "/>\n";
661                     }
662                 }
663 
664                 t << "        <source>"
665                   << evilBytes(msg.sourceText(), isUtf8, format, codecName)
666                   << "</source>\n";
667 
668                 if (format != 11 && !msg.oldSourceText().isEmpty())
669                     t << "        <oldsource>" << protect(msg.oldSourceText()) << "</oldsource>\n";
670 
671                 if (!msg.comment().isEmpty()) {
672                     t << "        <comment>"
673                       << evilBytes(msg.comment(), isUtf8, format, codecName)
674                       << "</comment>\n";
675                 }
676 
677                 if (format != 11) {
678 
679                     if (!msg.oldComment().isEmpty())
680                         t << "        <oldcomment>" << protect(msg.oldComment()) << "</oldcomment>\n";
681 
682                     if (!msg.extraComment().isEmpty())
683                         t << "        <extracomment>" << protect(msg.extraComment())
684                           << "</extracomment>\n";
685 
686                     if (!msg.translatorComment().isEmpty())
687                         t << "        <translatorcomment>" << protect(msg.translatorComment())
688                           << "</translatorcomment>\n";
689 
690                 }
691 
692                 t << "        <translation";
693                 if (msg.type() == TranslatorMessage::Unfinished)
694                     t << " type=\"unfinished\"";
695                 else if (msg.type() == TranslatorMessage::Obsolete)
696                     t << " type=\"obsolete\"";
697                 if (msg.isPlural()) {
698                     t << ">";
699                     const QStringList &translns = msg.translations();
700                     for (int j = 0; j < translns.count(); ++j) {
701                         t << "\n            <numerusform";
702                         writeVariants(t, "            ", translns[j]);
703                         t << "</numerusform>";
704                     }
705                     t << "\n        ";
706                 } else {
707                     writeVariants(t, "        ", msg.translation());
708                 }
709                 t << "</translation>\n";
710 
711                 if (format != 11)
712                     writeExtras(t, "        ", msg.extras(), drops);
713 
714                 if (!msg.userData().isEmpty())
715                     t << "        <userdata>" << msg.userData() << "</userdata>\n";
716                 t << "    </message>\n";
717 
718                 if (format != 11 || second || !msg.isUtf8() || !msg.isNonUtf8())
719                     break;
720                 isUtf8 = false;
721                 second = true;
722             }
723         }
724         t << "</context>\n";
725     }
726 
727     t << "</TS>\n";
728     return result;
729 }
730 
loadTS(Translator & translator,QIODevice & dev,ConversionData & cd)731 bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd)
732 {
733     TSReader reader(dev, cd);
734     return reader.read(translator);
735 }
736 
saveTS11(const Translator & translator,QIODevice & dev,ConversionData & cd)737 bool saveTS11(const Translator &translator, QIODevice &dev, ConversionData &cd)
738 {
739     return saveTS(translator, dev, cd, 11);
740 }
741 
saveTS20(const Translator & translator,QIODevice & dev,ConversionData & cd)742 bool saveTS20(const Translator &translator, QIODevice &dev, ConversionData &cd)
743 {
744     return saveTS(translator, dev, cd, 20);
745 }
746 
initTS()747 int initTS()
748 {
749     Translator::FileFormat format;
750 
751     format.extension = QLatin1String("ts11");
752     format.fileType = Translator::FileFormat::TranslationSource;
753     format.priority = -1;
754     format.description = QObject::tr("Qt translation sources (format 1.1)");
755     format.loader = &loadTS;
756     format.saver = &saveTS11;
757     Translator::registerFileFormat(format);
758 
759     format.extension = QLatin1String("ts20");
760     format.fileType = Translator::FileFormat::TranslationSource;
761     format.priority = -1;
762     format.description = QObject::tr("Qt translation sources (format 2.0)");
763     format.loader = &loadTS;
764     format.saver = &saveTS20;
765     Translator::registerFileFormat(format);
766 
767     // "ts" is always the latest. right now it's ts20.
768     format.extension = QLatin1String("ts");
769     format.fileType = Translator::FileFormat::TranslationSource;
770     format.priority = 0;
771     format.description = QObject::tr("Qt translation sources (latest format)");
772     format.loader = &loadTS;
773     format.saver = &saveTS20;
774     Translator::registerFileFormat(format);
775 
776     return 1;
777 }
778 
779 Q_CONSTRUCTOR_FUNCTION(initTS)
780 
781 QT_END_NAMESPACE
782