tools/linguist/shared/ts.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Linguist of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    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 
       
    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:
       
    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:
       
    88     bool elementStarts(const QString &str) const
       
    89     {
       
    90         return isStartElement() && name() == str;
       
    91     }
       
    92 
       
    93     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 
       
   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 
       
   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 
       
   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 
       
   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 
       
   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 
       
   268             QXmlStreamAttributes atts = attributes();
       
   269             //QString version = atts.value(strversion).toString();
       
   270             translator.setLanguageCode(atts.value(strlanguage).toString());
       
   271             translator.setSourceLanguageCode(atts.value(strsourcelanguage).toString());
       
   272             while (!atEnd()) {
       
   273                 readNext();
       
   274                 if (isEndElement()) {
       
   275                     // </TS> found, finish local loop
       
   276                     break;
       
   277                 } else if (isWhiteSpace()) {
       
   278                     // ignore these, just whitespace
       
   279                 } else if (elementStarts(strdefaultcodec)) {
       
   280                     // <defaultcodec>
       
   281                     const QString &codec = readElementText();
       
   282                     if (!codec.isEmpty())
       
   283                         translator.setCodecName(codec.toLatin1());
       
   284                     // </defaultcodec>
       
   285                 } else if (isStartElement()
       
   286                         && name().toString().startsWith(strextrans)) {
       
   287                     // <extra-...>
       
   288                     QString tag = name().toString();
       
   289                     translator.setExtra(tag.mid(6), readContents());
       
   290                     // </extra-...>
       
   291                 } else if (elementStarts(strcontext)) {
       
   292                     // <context>
       
   293                     QString context;
       
   294                     while (!atEnd()) {
       
   295                         readNext();
       
   296                         if (isEndElement()) {
       
   297                             // </context> found, finish local loop
       
   298                             break;
       
   299                         } else if (isWhiteSpace()) {
       
   300                             // ignore these, just whitespace
       
   301                         } else if (elementStarts(strname)) {
       
   302                             // <name>
       
   303                             context = readElementText();
       
   304                             // </name>
       
   305                         } else if (elementStarts(strmessage)) {
       
   306                             // <message>
       
   307                             TranslatorMessage::References refs;
       
   308                             QString currentMsgFile = currentFile;
       
   309 
       
   310                             TranslatorMessage msg;
       
   311                             msg.setId(attributes().value(strid).toString());
       
   312                             msg.setContext(context);
       
   313                             msg.setType(TranslatorMessage::Finished);
       
   314                             msg.setPlural(attributes().value(strnumerus) == stryes);
       
   315                             const QStringRef &utf8Attr = attributes().value(strutf8);
       
   316                             msg.setNonUtf8(utf8Attr == strboth);
       
   317                             msg.setUtf8(msg.isNonUtf8() || utf8Attr == strtrue
       
   318                                  ||  attributes().value(strencoding) == strUtf8);
       
   319                             while (!atEnd()) {
       
   320                                 readNext();
       
   321                                 if (isEndElement()) {
       
   322                                     // </message> found, finish local loop
       
   323                                     msg.setReferences(refs);
       
   324                                     translator.append(msg);
       
   325                                     break;
       
   326                                 } else if (isWhiteSpace()) {
       
   327                                     // ignore these, just whitespace
       
   328                                 } else if (elementStarts(strsource)) {
       
   329                                     // <source>...</source>
       
   330                                     msg.setSourceText(readContents());
       
   331                                 } else if (elementStarts(stroldsource)) {
       
   332                                     // <oldsource>...</oldsource>
       
   333                                     msg.setOldSourceText(readContents());
       
   334                                 } else if (elementStarts(stroldcomment)) {
       
   335                                     // <oldcomment>...</oldcomment>
       
   336                                     msg.setOldComment(readContents());
       
   337                                 } else if (elementStarts(strextracomment)) {
       
   338                                     // <extracomment>...</extracomment>
       
   339                                     msg.setExtraComment(readContents());
       
   340                                 } else if (elementStarts(strtranslatorcomment)) {
       
   341                                     // <translatorcomment>...</translatorcomment>
       
   342                                     msg.setTranslatorComment(readContents());
       
   343                                 } else if (elementStarts(strlocation)) {
       
   344                                     // <location/>
       
   345                                     QXmlStreamAttributes atts = attributes();
       
   346                                     QString fileName = atts.value(strfilename).toString();
       
   347                                     if (fileName.isEmpty()) {
       
   348                                         fileName = currentMsgFile;
       
   349                                     } else {
       
   350                                         if (refs.isEmpty())
       
   351                                             currentFile = fileName;
       
   352                                         currentMsgFile = fileName;
       
   353                                     }
       
   354                                     const QString lin = atts.value(strline).toString();
       
   355                                     if (lin.isEmpty()) {
       
   356                                         translator.setLocationsType(Translator::RelativeLocations);
       
   357                                         refs.append(TranslatorMessage::Reference(fileName, -1));
       
   358                                     } else {
       
   359                                         bool bOK;
       
   360                                         int lineNo = lin.toInt(&bOK);
       
   361                                         if (bOK) {
       
   362                                             if (lin.startsWith(QLatin1Char('+')) || lin.startsWith(QLatin1Char('-'))) {
       
   363                                                 lineNo = (currentLine[fileName] += lineNo);
       
   364                                                 translator.setLocationsType(Translator::RelativeLocations);
       
   365                                             } else {
       
   366                                                 translator.setLocationsType(Translator::AbsoluteLocations);
       
   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             } // </TS>
       
   426         } else {
       
   427             handleError();
       
   428         }
       
   429     }
       
   430     if (hasError()) {
       
   431         m_cd.appendError(errorString());
       
   432         return false;
       
   433     }
       
   434     return true;
       
   435 }
       
   436 
       
   437 static QString numericEntity(int ch)
       
   438 {
       
   439     return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>")
       
   440         : QLatin1String("&#x%1;")) .arg(ch, 0, 16);
       
   441 }
       
   442 
       
   443 static QString protect(const QString &str)
       
   444 {
       
   445     QString result;
       
   446     result.reserve(str.length() * 12 / 10);
       
   447     for (int i = 0; i != str.size(); ++i) {
       
   448         uint c = str.at(i).unicode();
       
   449         switch (c) {
       
   450         case '\"':
       
   451             result += QLatin1String("&quot;");
       
   452             break;
       
   453         case '&':
       
   454             result += QLatin1String("&amp;");
       
   455             break;
       
   456         case '>':
       
   457             result += QLatin1String("&gt;");
       
   458             break;
       
   459         case '<':
       
   460             result += QLatin1String("&lt;");
       
   461             break;
       
   462         case '\'':
       
   463             result += QLatin1String("&apos;");
       
   464             break;
       
   465         default:
       
   466             if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
       
   467                 result += numericEntity(c);
       
   468             else // this also covers surrogates
       
   469                 result += QChar(c);
       
   470         }
       
   471     }
       
   472     return result;
       
   473 }
       
   474 
       
   475 static QString evilBytes(const QString& str,
       
   476     bool isUtf8, int format, const QByteArray &codecName)
       
   477 {
       
   478     //qDebug() << "EVIL: " << str << isUtf8 << format << codecName;
       
   479     if (isUtf8)
       
   480         return protect(str);
       
   481     if (format == 20)
       
   482         return protect(str);
       
   483     if (codecName == "UTF-8")
       
   484         return protect(str);
       
   485     QTextCodec *codec = QTextCodec::codecForName(codecName);
       
   486     if (!codec)
       
   487         return protect(str);
       
   488     QString t = QString::fromLatin1(codec->fromUnicode(protect(str)).data());
       
   489     int len = (int) t.length();
       
   490     QString result;
       
   491     // FIXME: Factor is sensible only for latin scripts, probably.
       
   492     result.reserve(t.length() * 2);
       
   493     for (int k = 0; k < len; k++) {
       
   494         if (t[k].unicode() >= 0x7f)
       
   495             result += numericEntity(t[k].unicode());
       
   496         else
       
   497             result += t[k];
       
   498     }
       
   499     return result;
       
   500 }
       
   501 
       
   502 static void writeExtras(QTextStream &t, const char *indent,
       
   503                         const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
       
   504 {
       
   505     for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
       
   506         if (!drops.exactMatch(it.key())) {
       
   507             t << indent << "<extra-" << it.key() << '>'
       
   508               << protect(it.value())
       
   509               << "</extra-" << it.key() << ">\n";
       
   510         }
       
   511     }
       
   512 }
       
   513 
       
   514 static void writeVariants(QTextStream &t, const char *indent, const QString &input)
       
   515 {
       
   516     int offset;
       
   517     if ((offset = input.indexOf(QChar(Translator::BinaryVariantSeparator))) >= 0) {
       
   518         t << " variants=\"yes\">";
       
   519         int start = 0;
       
   520         forever {
       
   521             t << "\n    " << indent << "<lengthvariant>"
       
   522               << protect(input.mid(start, offset - start))
       
   523               << "</lengthvariant>";
       
   524             if (offset == input.length())
       
   525                 break;
       
   526             start = offset + 1;
       
   527             offset = input.indexOf(QChar(Translator::BinaryVariantSeparator), start);
       
   528             if (offset < 0)
       
   529                 offset = input.length();
       
   530         }
       
   531         t << "\n" << indent;
       
   532     } else {
       
   533         t << ">" << protect(input);
       
   534     }
       
   535 }
       
   536 
       
   537 bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd, int format)
       
   538 {
       
   539     bool result = true;
       
   540     QTextStream t(&dev);
       
   541     t.setCodec(QTextCodec::codecForName("UTF-8"));
       
   542     bool trIsUtf8 = (translator.codecName() == "UTF-8");
       
   543     //qDebug() << translator.codecName();
       
   544     bool fileIsUtf8 = (format == 20 || trIsUtf8);
       
   545 
       
   546     // The xml prolog allows processors to easily detect the correct encoding
       
   547     t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n";
       
   548 
       
   549     if (format == 11)
       
   550         t << "<TS version=\"1.1\"";
       
   551     else
       
   552         t << "<TS version=\"2.0\"";
       
   553 
       
   554     QString languageCode = translator.languageCode();
       
   555     if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
       
   556         t << " language=\"" << languageCode << "\"";
       
   557     if (format == 20) {
       
   558         languageCode = translator.sourceLanguageCode();
       
   559         if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
       
   560             t << " sourcelanguage=\"" << languageCode << "\"";
       
   561     }
       
   562     t << ">\n";
       
   563 
       
   564     QByteArray codecName = translator.codecName();
       
   565     if (codecName != "ISO-8859-1")
       
   566         t << "<defaultcodec>" << codecName << "</defaultcodec>\n";
       
   567 
       
   568     QRegExp drops(cd.dropTags().join(QLatin1String("|")));
       
   569 
       
   570     if (format == 20)
       
   571         writeExtras(t, "    ", translator.extras(), drops);
       
   572 
       
   573     QHash<QString, QList<TranslatorMessage> > messageOrder;
       
   574     QList<QString> contextOrder;
       
   575     foreach (const TranslatorMessage &msg, translator.messages()) {
       
   576         // no need for such noise
       
   577         if (msg.type() == TranslatorMessage::Obsolete && msg.translation().isEmpty())
       
   578             continue;
       
   579 
       
   580         QList<TranslatorMessage> &context = messageOrder[msg.context()];
       
   581         if (context.isEmpty())
       
   582             contextOrder.append(msg.context());
       
   583         context.append(msg);
       
   584     }
       
   585     if (cd.sortContexts())
       
   586         qSort(contextOrder);
       
   587 
       
   588     QHash<QString, int> currentLine;
       
   589     QString currentFile;
       
   590     foreach (const QString &context, contextOrder) {
       
   591         const TranslatorMessage &firstMsg = messageOrder[context].first();
       
   592         t << "<context" << ((!fileIsUtf8 && firstMsg.isUtf8()) ? " encoding=\"UTF-8\"" : "") << ">\n";
       
   593 
       
   594         t << "    <name>"
       
   595           << evilBytes(context, firstMsg.isUtf8() || fileIsUtf8, format, codecName)
       
   596           << "</name>\n";
       
   597         foreach (const TranslatorMessage &msg, messageOrder[context]) {
       
   598             //msg.dump();
       
   599 
       
   600             bool isUtf8 = msg.isUtf8();
       
   601             bool second = false;
       
   602             forever {
       
   603 
       
   604                 t << "    <message";
       
   605                 if (!msg.id().isEmpty())
       
   606                     t << " id=\"" << msg.id() << "\"";
       
   607                 if (!trIsUtf8) {
       
   608                     if (format == 11) {
       
   609                         if (isUtf8)
       
   610                             t << " encoding=\"UTF-8\"";
       
   611                     } else {
       
   612                         if (msg.isUtf8()) {
       
   613                             if (msg.isNonUtf8())
       
   614                                 t << " utf8=\"both\"";
       
   615                             else
       
   616                                 t << " utf8=\"true\"";
       
   617                         }
       
   618                     }
       
   619                 }
       
   620                 if (msg.isPlural())
       
   621                     t << " numerus=\"yes\"";
       
   622                 t << ">\n";
       
   623                 if (translator.locationsType() != Translator::NoLocations) {
       
   624                     QString cfile = currentFile;
       
   625                     bool first = true;
       
   626                     foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) {
       
   627                         QString fn = cd.m_targetDir.relativeFilePath(ref.fileName())
       
   628                                     .replace(QLatin1Char('\\'),QLatin1Char('/'));
       
   629                         int ln = ref.lineNumber();
       
   630                         QString ld;
       
   631                         if (translator.locationsType() == Translator::RelativeLocations) {
       
   632                             if (ln != -1) {
       
   633                                 int dlt = ln - currentLine[fn];
       
   634                                 if (dlt >= 0)
       
   635                                     ld.append(QLatin1Char('+'));
       
   636                                 ld.append(QString::number(dlt));
       
   637                                 currentLine[fn] = ln;
       
   638                             }
       
   639 
       
   640                             if (fn != cfile) {
       
   641                                 if (first)
       
   642                                     currentFile = fn;
       
   643                                 cfile = fn;
       
   644                             } else {
       
   645                                 fn.clear();
       
   646                             }
       
   647                             first = false;
       
   648                         } else {
       
   649                             if (ln != -1)
       
   650                                 ld = QString::number(ln);
       
   651                         }
       
   652                         t << "        <location";
       
   653                         if (!fn.isEmpty())
       
   654                             t << " filename=\"" << fn << "\"";
       
   655                         if (!ld.isEmpty())
       
   656                             t << " line=\"" << ld << "\"";
       
   657                         t << "/>\n";
       
   658                     }
       
   659                 }
       
   660 
       
   661                 t << "        <source>"
       
   662                   << evilBytes(msg.sourceText(), isUtf8, format, codecName)
       
   663                   << "</source>\n";
       
   664 
       
   665                 if (format != 11 && !msg.oldSourceText().isEmpty())
       
   666                     t << "        <oldsource>" << protect(msg.oldSourceText()) << "</oldsource>\n";
       
   667 
       
   668                 if (!msg.comment().isEmpty()) {
       
   669                     t << "        <comment>"
       
   670                       << evilBytes(msg.comment(), isUtf8, format, codecName)
       
   671                       << "</comment>\n";
       
   672                 }
       
   673 
       
   674                 if (format != 11) {
       
   675 
       
   676                     if (!msg.oldComment().isEmpty())
       
   677                         t << "        <oldcomment>" << protect(msg.oldComment()) << "</oldcomment>\n";
       
   678 
       
   679                     if (!msg.extraComment().isEmpty())
       
   680                         t << "        <extracomment>" << protect(msg.extraComment())
       
   681                           << "</extracomment>\n";
       
   682 
       
   683                     if (!msg.translatorComment().isEmpty())
       
   684                         t << "        <translatorcomment>" << protect(msg.translatorComment())
       
   685                           << "</translatorcomment>\n";
       
   686 
       
   687                 }
       
   688 
       
   689                 t << "        <translation";
       
   690                 if (msg.type() == TranslatorMessage::Unfinished)
       
   691                     t << " type=\"unfinished\"";
       
   692                 else if (msg.type() == TranslatorMessage::Obsolete)
       
   693                     t << " type=\"obsolete\"";
       
   694                 if (msg.isPlural()) {
       
   695                     t << ">";
       
   696                     const QStringList &translns = msg.translations();
       
   697                     for (int j = 0; j < translns.count(); ++j) {
       
   698                         t << "\n            <numerusform";
       
   699                         writeVariants(t, "            ", translns[j]);
       
   700                         t << "</numerusform>";
       
   701                     }
       
   702                     t << "\n        ";
       
   703                 } else {
       
   704                     writeVariants(t, "        ", msg.translation());
       
   705                 }
       
   706                 t << "</translation>\n";
       
   707 
       
   708                 if (format != 11)
       
   709                     writeExtras(t, "        ", msg.extras(), drops);
       
   710 
       
   711                 if (!msg.userData().isEmpty())
       
   712                     t << "        <userdata>" << msg.userData() << "</userdata>\n";
       
   713                 t << "    </message>\n";
       
   714 
       
   715                 if (format != 11 || second || !msg.isUtf8() || !msg.isNonUtf8())
       
   716                     break;
       
   717                 isUtf8 = false;
       
   718                 second = true;
       
   719             }
       
   720         }
       
   721         t << "</context>\n";
       
   722     }
       
   723 
       
   724     t << "</TS>\n";
       
   725     return result;
       
   726 }
       
   727 
       
   728 bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd)
       
   729 {
       
   730     translator.setLocationsType(Translator::NoLocations);
       
   731     TSReader reader(dev, cd);
       
   732     return reader.read(translator);
       
   733 }
       
   734 
       
   735 bool saveTS11(const Translator &translator, QIODevice &dev, ConversionData &cd)
       
   736 {
       
   737     return saveTS(translator, dev, cd, 11);
       
   738 }
       
   739 
       
   740 bool saveTS20(const Translator &translator, QIODevice &dev, ConversionData &cd)
       
   741 {
       
   742     return saveTS(translator, dev, cd, 20);
       
   743 }
       
   744 
       
   745 int initTS()
       
   746 {
       
   747     Translator::FileFormat format;
       
   748 
       
   749     format.extension = QLatin1String("ts11");
       
   750     format.fileType = Translator::FileFormat::TranslationSource;
       
   751     format.priority = -1;
       
   752     format.description = QObject::tr("Qt translation sources (format 1.1)");
       
   753     format.loader = &loadTS;
       
   754     format.saver = &saveTS11;
       
   755     Translator::registerFileFormat(format);
       
   756 
       
   757     format.extension = QLatin1String("ts20");
       
   758     format.fileType = Translator::FileFormat::TranslationSource;
       
   759     format.priority = -1;
       
   760     format.description = QObject::tr("Qt translation sources (format 2.0)");
       
   761     format.loader = &loadTS;
       
   762     format.saver = &saveTS20;
       
   763     Translator::registerFileFormat(format);
       
   764 
       
   765     // "ts" is always the latest. right now it's ts20.
       
   766     format.extension = QLatin1String("ts");
       
   767     format.fileType = Translator::FileFormat::TranslationSource;
       
   768     format.priority = 0;
       
   769     format.description = QObject::tr("Qt translation sources (latest format)");
       
   770     format.loader = &loadTS;
       
   771     format.saver = &saveTS20;
       
   772     Translator::registerFileFormat(format);
       
   773 
       
   774     return 1;
       
   775 }
       
   776 
       
   777 Q_CONSTRUCTOR_FUNCTION(initTS)
       
   778 
       
   779 QT_END_NAMESPACE