tools/linguist/shared/xliff.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/QDebug>
       
    45 #include <QtCore/QMap>
       
    46 #include <QtCore/QStack>
       
    47 #include <QtCore/QString>
       
    48 #include <QtCore/QTextCodec>
       
    49 #include <QtCore/QTextStream>
       
    50 
       
    51 #include <QtXml/QXmlAttributes>
       
    52 #include <QtXml/QXmlDefaultHandler>
       
    53 #include <QtXml/QXmlParseException>
       
    54 
       
    55 
       
    56 QT_BEGIN_NAMESPACE
       
    57 
       
    58 /**
       
    59  * Implementation of XLIFF file format for Linguist
       
    60  */
       
    61 //static const char *restypeDomain = "x-gettext-domain";
       
    62 static const char *restypeContext = "x-trolltech-linguist-context";
       
    63 static const char *restypePlurals = "x-gettext-plurals";
       
    64 static const char *restypeDummy = "x-dummy";
       
    65 static const char *dataTypeUIFile = "x-trolltech-designer-ui";
       
    66 static const char *contextMsgctxt = "x-gettext-msgctxt"; // XXX Troll invention, so far.
       
    67 static const char *contextOldMsgctxt = "x-gettext-previous-msgctxt"; // XXX Troll invention, so far.
       
    68 static const char *attribPlural = "trolltech:plural";
       
    69 static const char *XLIFF11namespaceURI = "urn:oasis:names:tc:xliff:document:1.1";
       
    70 static const char *XLIFF12namespaceURI = "urn:oasis:names:tc:xliff:document:1.2";
       
    71 static const char *TrollTsNamespaceURI = "urn:trolltech:names:ts:document:1.0";
       
    72 
       
    73 #define COMBINE4CHARS(c1, c2, c3, c4) \
       
    74     (int(c1) << 24 | int(c2) << 16 | int(c3) << 8 | int(c4) )
       
    75 
       
    76 static QString dataType(const TranslatorMessage &m)
       
    77 {
       
    78     QByteArray fileName = m.fileName().toAscii();
       
    79     unsigned int extHash = 0;
       
    80     int pos = fileName.count() - 1;
       
    81     for (int pass = 0; pass < 4 && pos >=0; ++pass, --pos) {
       
    82         if (fileName.at(pos) == '.')
       
    83             break;
       
    84         extHash |= ((int)fileName.at(pos) << (8*pass));
       
    85     }
       
    86 
       
    87     switch (extHash) {
       
    88         case COMBINE4CHARS(0,'c','p','p'):
       
    89         case COMBINE4CHARS(0,'c','x','x'):
       
    90         case COMBINE4CHARS(0,'c','+','+'):
       
    91         case COMBINE4CHARS(0,'h','p','p'):
       
    92         case COMBINE4CHARS(0,'h','x','x'):
       
    93         case COMBINE4CHARS(0,'h','+','+'):
       
    94             return QLatin1String("cpp");
       
    95         case COMBINE4CHARS(0, 0 , 0 ,'c'):
       
    96         case COMBINE4CHARS(0, 0 , 0 ,'h'):
       
    97         case COMBINE4CHARS(0, 0 ,'c','c'):
       
    98         case COMBINE4CHARS(0, 0 ,'c','h'):
       
    99         case COMBINE4CHARS(0, 0 ,'h','h'):
       
   100             return QLatin1String("c");
       
   101         case COMBINE4CHARS(0, 0 ,'u','i'):
       
   102             return QLatin1String(dataTypeUIFile);   //### form?
       
   103         default:
       
   104             return QLatin1String("plaintext");      // we give up
       
   105     }
       
   106 }
       
   107 
       
   108 static void writeIndent(QTextStream &ts, int indent)
       
   109 {
       
   110     ts << QString().fill(QLatin1Char(' '), indent * 2);
       
   111 }
       
   112 
       
   113 struct CharMnemonic
       
   114 {
       
   115     char ch;
       
   116     char escape;
       
   117     const char *mnemonic;
       
   118 };
       
   119 
       
   120 static const CharMnemonic charCodeMnemonics[] = {
       
   121     {0x07, 'a', "bel"},
       
   122     {0x08, 'b', "bs"},
       
   123     {0x09, 't', "tab"},
       
   124     {0x0a, 'n', "lf"},
       
   125     {0x0b, 'v', "vt"},
       
   126     {0x0c, 'f', "ff"},
       
   127     {0x0d, 'r', "cr"}
       
   128 };
       
   129 
       
   130 static char charFromEscape(char escape)
       
   131 {
       
   132     for (uint i = 0; i < sizeof(charCodeMnemonics)/sizeof(CharMnemonic); ++i) {
       
   133         CharMnemonic cm =  charCodeMnemonics[i];
       
   134         if (cm.escape == escape)
       
   135             return cm.ch;
       
   136     }
       
   137     Q_ASSERT(0);
       
   138     return escape;
       
   139 }
       
   140 
       
   141 static QString numericEntity(int ch, bool makePhs)
       
   142 {
       
   143     // ### This needs to be reviewed, to reflect the updated XLIFF-PO spec.
       
   144     if (!makePhs || ch < 7 || ch > 0x0d)
       
   145         return QString::fromAscii("&#x%1;").arg(QString::number(ch, 16));
       
   146 
       
   147     CharMnemonic cm = charCodeMnemonics[int(ch) - 7];
       
   148     QString name = QLatin1String(cm.mnemonic);
       
   149     char escapechar = cm.escape;
       
   150 
       
   151     static int id = 0;
       
   152     return QString::fromAscii("<ph id=\"ph%1\" ctype=\"x-ch-%2\">\\%3</ph>")
       
   153               .arg(++id) .arg(name) .arg(escapechar);
       
   154 }
       
   155 
       
   156 static QString protect(const QString &str, bool makePhs = true)
       
   157 {
       
   158     QString result;
       
   159     int len = str.size();
       
   160     for (int i = 0; i != len; ++i) {
       
   161         uint c = str.at(i).unicode();
       
   162         switch (c) {
       
   163         case '\"':
       
   164             result += QLatin1String("&quot;");
       
   165             break;
       
   166         case '&':
       
   167             result += QLatin1String("&amp;");
       
   168             break;
       
   169         case '>':
       
   170             result += QLatin1String("&gt;");
       
   171             break;
       
   172         case '<':
       
   173             result += QLatin1String("&lt;");
       
   174             break;
       
   175         case '\'':
       
   176             result += QLatin1String("&apos;");
       
   177             break;
       
   178         default:
       
   179             if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
       
   180                 result += numericEntity(c, makePhs);
       
   181             else // this also covers surrogates
       
   182                 result += QChar(c);
       
   183         }
       
   184     }
       
   185     return result;
       
   186 }
       
   187 
       
   188 
       
   189 static void writeExtras(QTextStream &ts, int indent,
       
   190                         const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
       
   191 {
       
   192     for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
       
   193         if (!drops.exactMatch(it.key())) {
       
   194             writeIndent(ts, indent);
       
   195             ts << "<trolltech:" << it.key() << '>'
       
   196                << protect(it.value())
       
   197                << "</trolltech:" << it.key() << ">\n";
       
   198         }
       
   199     }
       
   200 }
       
   201 
       
   202 static void writeLineNumber(QTextStream &ts, const TranslatorMessage &msg, int indent)
       
   203 {
       
   204     if (msg.lineNumber() == -1)
       
   205         return;
       
   206     writeIndent(ts, indent);
       
   207     ts << "<context-group purpose=\"location\"><context context-type=\"linenumber\">"
       
   208        << msg.lineNumber() << "</context></context-group>\n";
       
   209     foreach (const TranslatorMessage::Reference &ref, msg.extraReferences()) {
       
   210         writeIndent(ts, indent);
       
   211         ts << "<context-group purpose=\"location\">";
       
   212         if (ref.fileName() != msg.fileName())
       
   213             ts << "<context context-type=\"sourcefile\">" << ref.fileName() << "</context>";
       
   214         ts << "<context context-type=\"linenumber\">" << ref.lineNumber()
       
   215            << "</context></context-group>\n";
       
   216     }
       
   217 }
       
   218 
       
   219 static void writeComment(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
       
   220 {
       
   221     if (!msg.comment().isEmpty()) {
       
   222         writeIndent(ts, indent);
       
   223         ts << "<context-group><context context-type=\"" << contextMsgctxt << "\">"
       
   224            << protect(msg.comment(), false)
       
   225            << "</context></context-group>\n";
       
   226     }
       
   227     if (!msg.oldComment().isEmpty()) {
       
   228         writeIndent(ts, indent);
       
   229         ts << "<context-group><context context-type=\"" << contextOldMsgctxt << "\">"
       
   230            << protect(msg.oldComment(), false)
       
   231            << "</context></context-group>\n";
       
   232     }
       
   233     writeExtras(ts, indent, msg.extras(), drops);
       
   234     if (!msg.extraComment().isEmpty()) {
       
   235         writeIndent(ts, indent);
       
   236         ts << "<note annotates=\"source\" from=\"developer\">"
       
   237            << protect(msg.extraComment()) << "</note>\n";
       
   238     }
       
   239     if (!msg.translatorComment().isEmpty()) {
       
   240         writeIndent(ts, indent);
       
   241         ts << "<note from=\"translator\">"
       
   242            << protect(msg.translatorComment()) << "</note>\n";
       
   243     }
       
   244 }
       
   245 
       
   246 static void writeTransUnits(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
       
   247 {
       
   248     static int msgid;
       
   249     QString msgidstr = !msg.id().isEmpty() ? msg.id() : QString::fromAscii("_msg%1").arg(++msgid);
       
   250 
       
   251     QStringList translns = msg.translations();
       
   252     QHash<QString, QString>::const_iterator it;
       
   253     QString pluralStr;
       
   254     QStringList sources(msg.sourceText());
       
   255     if ((it = msg.extras().find(QString::fromLatin1("po-msgid_plural"))) != msg.extras().end())
       
   256         sources.append(*it);
       
   257     QStringList oldsources;
       
   258     if (!msg.oldSourceText().isEmpty())
       
   259         oldsources.append(msg.oldSourceText());
       
   260     if ((it = msg.extras().find(QString::fromLatin1("po-old_msgid_plural"))) != msg.extras().end()) {
       
   261         if (oldsources.isEmpty()) {
       
   262             if (sources.count() == 2)
       
   263                 oldsources.append(QString());
       
   264             else
       
   265                 pluralStr = QLatin1Char(' ') + QLatin1String(attribPlural) + QLatin1String("=\"yes\"");
       
   266         }
       
   267         oldsources.append(*it);
       
   268     }
       
   269 
       
   270     QStringList::const_iterator
       
   271         srcit = sources.begin(), srcend = sources.end(),
       
   272         oldsrcit = oldsources.begin(), oldsrcend = oldsources.end(),
       
   273         transit = translns.begin(), transend = translns.end();
       
   274     int plural = 0;
       
   275     QString source;
       
   276     while (srcit != srcend || oldsrcit != oldsrcend || transit != transend) {
       
   277         QByteArray attribs;
       
   278         QByteArray state;
       
   279         if (msg.type() == TranslatorMessage::Obsolete) {
       
   280             if (!msg.isPlural())
       
   281                 attribs = " translate=\"no\"";
       
   282         } else if (msg.type() == TranslatorMessage::Finished) {
       
   283             attribs = " approved=\"yes\"";
       
   284         } else if (transit != transend && !transit->isEmpty()) {
       
   285             state = " state=\"needs-review-translation\"";
       
   286         }
       
   287         writeIndent(ts, indent);
       
   288         ts << "<trans-unit id=\"" << msgidstr;
       
   289         if (msg.isPlural())
       
   290             ts << "[" << plural++ << "]";
       
   291         ts << "\"" << attribs << ">\n";
       
   292         ++indent;
       
   293 
       
   294         writeIndent(ts, indent);
       
   295         if (srcit != srcend) {
       
   296             source = *srcit;
       
   297             ++srcit;
       
   298         } // else just repeat last element
       
   299         ts << "<source xml:space=\"preserve\">" << protect(source) << "</source>\n";
       
   300 
       
   301         bool puttrans = false;
       
   302         QString translation;
       
   303         if (transit != transend) {
       
   304             translation = *transit;
       
   305             translation.replace(QChar(Translator::BinaryVariantSeparator),
       
   306                                 QChar(Translator::TextVariantSeparator));
       
   307             ++transit;
       
   308             puttrans = true;
       
   309         }
       
   310         do {
       
   311             if (oldsrcit != oldsrcend && !oldsrcit->isEmpty()) {
       
   312                 writeIndent(ts, indent);
       
   313                 ts << "<alt-trans>\n";
       
   314                 ++indent;
       
   315                 writeIndent(ts, indent);
       
   316                 ts << "<source xml:space=\"preserve\"" << pluralStr << '>' << protect(*oldsrcit) << "</source>\n";
       
   317                 if (!puttrans) {
       
   318                     writeIndent(ts, indent);
       
   319                     ts << "<target restype=\"" << restypeDummy << "\"/>\n";
       
   320                 }
       
   321             }
       
   322 
       
   323             if (puttrans) {
       
   324                 writeIndent(ts, indent);
       
   325                 ts << "<target xml:space=\"preserve\"" << state << ">" << protect(translation) << "</target>\n";
       
   326             }
       
   327 
       
   328             if (oldsrcit != oldsrcend) {
       
   329                 if (!oldsrcit->isEmpty()) {
       
   330                     --indent;
       
   331                     writeIndent(ts, indent);
       
   332                     ts << "</alt-trans>\n";
       
   333                 }
       
   334                 ++oldsrcit;
       
   335             }
       
   336 
       
   337             puttrans = false;
       
   338         } while (srcit == srcend && oldsrcit != oldsrcend);
       
   339 
       
   340         if (!msg.isPlural()) {
       
   341             writeLineNumber(ts, msg, indent);
       
   342             writeComment(ts, msg, drops, indent);
       
   343         }
       
   344 
       
   345         --indent;
       
   346         writeIndent(ts, indent);
       
   347         ts << "</trans-unit>\n";
       
   348     }
       
   349 }
       
   350 
       
   351 static void writeMessage(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
       
   352 {
       
   353     if (msg.isPlural()) {
       
   354         writeIndent(ts, indent);
       
   355         ts << "<group restype=\"" << restypePlurals << "\"";
       
   356         if (!msg.id().isEmpty())
       
   357             ts << " id=\"" << msg.id() << "\"";
       
   358         if (msg.type() == TranslatorMessage::Obsolete)
       
   359             ts << " translate=\"no\"";
       
   360         ts << ">\n";
       
   361         ++indent;
       
   362         writeLineNumber(ts, msg, indent);
       
   363         writeComment(ts, msg, drops, indent);
       
   364 
       
   365         writeTransUnits(ts, msg, drops, indent);
       
   366         --indent;
       
   367         writeIndent(ts, indent);
       
   368         ts << "</group>\n";
       
   369     } else {
       
   370         writeTransUnits(ts, msg, drops, indent);
       
   371     }
       
   372 }
       
   373 
       
   374 
       
   375 class XLIFFHandler : public QXmlDefaultHandler
       
   376 {
       
   377 public:
       
   378     XLIFFHandler(Translator &translator, ConversionData &cd);
       
   379 
       
   380     bool startElement(const QString& namespaceURI, const QString &localName,
       
   381         const QString &qName, const QXmlAttributes &atts );
       
   382     bool endElement(const QString& namespaceURI, const QString &localName,
       
   383         const QString &qName );
       
   384     bool characters(const QString &ch);
       
   385     bool fatalError(const QXmlParseException &exception);
       
   386 
       
   387     bool endDocument();
       
   388 
       
   389 private:
       
   390     enum XliffContext {
       
   391         XC_xliff,
       
   392         XC_group,
       
   393         XC_trans_unit,
       
   394         XC_context_group,
       
   395         XC_context_group_any,
       
   396         XC_context,
       
   397         XC_context_filename,
       
   398         XC_context_linenumber,
       
   399         XC_context_context,
       
   400         XC_context_comment,
       
   401         XC_context_old_comment,
       
   402         XC_ph,
       
   403         XC_extra_comment,
       
   404         XC_translator_comment,
       
   405         XC_restype_context,
       
   406         XC_restype_translation,
       
   407         XC_restype_plurals,
       
   408         XC_alt_trans
       
   409     };
       
   410     void pushContext(XliffContext ctx);
       
   411     bool popContext(XliffContext ctx);
       
   412     XliffContext currentContext() const;
       
   413     bool hasContext(XliffContext ctx) const;
       
   414     bool finalizeMessage(bool isPlural);
       
   415 
       
   416 private:
       
   417     Translator &m_translator;
       
   418     ConversionData &m_cd;
       
   419     TranslatorMessage::Type m_type;
       
   420     QString m_language;
       
   421     QString m_sourceLanguage;
       
   422     QString m_context;
       
   423     QString m_id;
       
   424     QStringList m_sources;
       
   425     QStringList m_oldSources;
       
   426     QString m_comment;
       
   427     QString m_oldComment;
       
   428     QString m_extraComment;
       
   429     QString m_translatorComment;
       
   430     bool m_isPlural;
       
   431     bool m_hadAlt;
       
   432     QStringList m_translations;
       
   433     QString m_fileName;
       
   434     int     m_lineNumber;
       
   435     QString m_extraFileName;
       
   436     TranslatorMessage::References m_refs;
       
   437     TranslatorMessage::ExtraData m_extra;
       
   438 
       
   439     QString accum;
       
   440     QString m_ctype;
       
   441     const QString m_URITT;  // convenience and efficiency
       
   442     const QString m_URI;  // ...
       
   443     const QString m_URI12;  // ...
       
   444     QStack<int> m_contextStack;
       
   445 };
       
   446 
       
   447 XLIFFHandler::XLIFFHandler(Translator &translator, ConversionData &cd)
       
   448   : m_translator(translator), m_cd(cd),
       
   449     m_type(TranslatorMessage::Finished),
       
   450     m_lineNumber(-1),
       
   451     m_URITT(QLatin1String(TrollTsNamespaceURI)),
       
   452     m_URI(QLatin1String(XLIFF11namespaceURI)),
       
   453     m_URI12(QLatin1String(XLIFF12namespaceURI))
       
   454 {}
       
   455 
       
   456 
       
   457 void XLIFFHandler::pushContext(XliffContext ctx)
       
   458 {
       
   459     m_contextStack.push_back(ctx);
       
   460 }
       
   461 
       
   462 // Only pops it off if the top of the stack contains ctx
       
   463 bool XLIFFHandler::popContext(XliffContext ctx)
       
   464 {
       
   465     if (!m_contextStack.isEmpty() && m_contextStack.top() == ctx) {
       
   466         m_contextStack.pop();
       
   467         return true;
       
   468     }
       
   469     return false;
       
   470 }
       
   471 
       
   472 XLIFFHandler::XliffContext XLIFFHandler::currentContext() const
       
   473 {
       
   474     if (!m_contextStack.isEmpty())
       
   475         return (XliffContext)m_contextStack.top();
       
   476     return XC_xliff;
       
   477 }
       
   478 
       
   479 // traverses to the top to check all of the parent contexes.
       
   480 bool XLIFFHandler::hasContext(XliffContext ctx) const
       
   481 {
       
   482     for (int i = m_contextStack.count() - 1; i >= 0; --i) {
       
   483         if (m_contextStack.at(i) == ctx)
       
   484             return true;
       
   485     }
       
   486     return false;
       
   487 }
       
   488 
       
   489 bool XLIFFHandler::startElement(const QString& namespaceURI,
       
   490     const QString &localName, const QString &qName, const QXmlAttributes &atts )
       
   491 {
       
   492     Q_UNUSED(qName);
       
   493     if (namespaceURI == m_URITT)
       
   494         goto bail;
       
   495     if (namespaceURI != m_URI && namespaceURI != m_URI12)
       
   496         return false;
       
   497     if (localName == QLatin1String("xliff")) {
       
   498         // make sure that the stack is not empty during parsing
       
   499         pushContext(XC_xliff);
       
   500     } else if (localName == QLatin1String("file")) {
       
   501         m_fileName = atts.value(QLatin1String("original"));
       
   502         m_language = atts.value(QLatin1String("target-language"));
       
   503         m_language.replace(QLatin1Char('-'), QLatin1Char('_'));
       
   504         m_sourceLanguage = atts.value(QLatin1String("source-language"));
       
   505         m_sourceLanguage.replace(QLatin1Char('-'), QLatin1Char('_'));
       
   506     } else if (localName == QLatin1String("group")) {
       
   507         if (atts.value(QLatin1String("restype")) == QLatin1String(restypeContext)) {
       
   508             m_context = atts.value(QLatin1String("resname"));
       
   509             pushContext(XC_restype_context);
       
   510         } else {
       
   511             if (atts.value(QLatin1String("restype")) == QLatin1String(restypePlurals)) {
       
   512                 pushContext(XC_restype_plurals);
       
   513                 m_id = atts.value(QLatin1String("id"));
       
   514                 if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
       
   515                     m_type = TranslatorMessage::Obsolete;
       
   516             } else {
       
   517                 pushContext(XC_group);
       
   518             }
       
   519         }
       
   520     } else if (localName == QLatin1String("trans-unit")) {
       
   521         if (!hasContext(XC_restype_plurals) || m_sources.isEmpty() /* who knows ... */)
       
   522             if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
       
   523                 m_type = TranslatorMessage::Obsolete;
       
   524         if (!hasContext(XC_restype_plurals)) {
       
   525             m_id = atts.value(QLatin1String("id"));
       
   526             if (m_id.startsWith(QLatin1String("_msg")))
       
   527                 m_id.clear();
       
   528         }
       
   529         if (m_type != TranslatorMessage::Obsolete &&
       
   530             atts.value(QLatin1String("approved")) != QLatin1String("yes"))
       
   531             m_type = TranslatorMessage::Unfinished;
       
   532         pushContext(XC_trans_unit);
       
   533         m_hadAlt = false;
       
   534     } else if (localName == QLatin1String("alt-trans")) {
       
   535         pushContext(XC_alt_trans);
       
   536     } else if (localName == QLatin1String("source")) {
       
   537         m_isPlural = atts.value(QLatin1String(attribPlural)) == QLatin1String("yes");
       
   538     } else if (localName == QLatin1String("target")) {
       
   539         if (atts.value(QLatin1String("restype")) != QLatin1String(restypeDummy))
       
   540             pushContext(XC_restype_translation);
       
   541     } else if (localName == QLatin1String("context-group")) {
       
   542         QString purpose = atts.value(QLatin1String("purpose"));
       
   543         if (purpose == QLatin1String("location"))
       
   544             pushContext(XC_context_group);
       
   545         else
       
   546             pushContext(XC_context_group_any);
       
   547     } else if (currentContext() == XC_context_group && localName == QLatin1String("context")) {
       
   548         QString ctxtype = atts.value(QLatin1String("context-type"));
       
   549         if (ctxtype == QLatin1String("linenumber"))
       
   550             pushContext(XC_context_linenumber);
       
   551         else if (ctxtype == QLatin1String("sourcefile"))
       
   552             pushContext(XC_context_filename);
       
   553     } else if (currentContext() == XC_context_group_any && localName == QLatin1String("context")) {
       
   554         QString ctxtype = atts.value(QLatin1String("context-type"));
       
   555         if (ctxtype == QLatin1String(contextMsgctxt))
       
   556             pushContext(XC_context_comment);
       
   557         else if (ctxtype == QLatin1String(contextOldMsgctxt))
       
   558             pushContext(XC_context_old_comment);
       
   559     } else if (localName == QLatin1String("note")) {
       
   560         if (atts.value(QLatin1String("annotates")) == QLatin1String("source") &&
       
   561             atts.value(QLatin1String("from")) == QLatin1String("developer"))
       
   562             pushContext(XC_extra_comment);
       
   563         else
       
   564             pushContext(XC_translator_comment);
       
   565     } else if (localName == QLatin1String("ph")) {
       
   566         QString ctype = atts.value(QLatin1String("ctype"));
       
   567         if (ctype.startsWith(QLatin1String("x-ch-")))
       
   568             m_ctype = ctype.mid(5);
       
   569         pushContext(XC_ph);
       
   570     }
       
   571 bail:
       
   572     if (currentContext() != XC_ph)
       
   573         accum.clear();
       
   574     return true;
       
   575 }
       
   576 
       
   577 bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localName,
       
   578     const QString &qName)
       
   579 {
       
   580     Q_UNUSED(qName);
       
   581     if (namespaceURI == m_URITT) {
       
   582         if (hasContext(XC_trans_unit) || hasContext(XC_restype_plurals))
       
   583             m_extra[localName] = accum;
       
   584         else
       
   585             m_translator.setExtra(localName, accum);
       
   586         return true;
       
   587     }
       
   588     if (namespaceURI != m_URI && namespaceURI != m_URI12)
       
   589         return false;
       
   590     //qDebug() << "URI:" <<  namespaceURI << "QNAME:" << qName;
       
   591     if (localName == QLatin1String("xliff")) {
       
   592         popContext(XC_xliff);
       
   593     } else if (localName == QLatin1String("source")) {
       
   594         if (hasContext(XC_alt_trans)) {
       
   595             if (m_isPlural && m_oldSources.isEmpty())
       
   596                 m_oldSources.append(QString());
       
   597             m_oldSources.append(accum);
       
   598             m_hadAlt = true;
       
   599         } else {
       
   600             m_sources.append(accum);
       
   601         }
       
   602     } else if (localName == QLatin1String("target")) {
       
   603         if (popContext(XC_restype_translation)) {
       
   604             accum.replace(QChar(Translator::TextVariantSeparator),
       
   605                           QChar(Translator::BinaryVariantSeparator));
       
   606             m_translations.append(accum);
       
   607         }
       
   608     } else if (localName == QLatin1String("context-group")) {
       
   609         if (popContext(XC_context_group)) {
       
   610             m_refs.append(TranslatorMessage::Reference(
       
   611                 m_extraFileName.isEmpty() ? m_fileName : m_extraFileName, m_lineNumber));
       
   612             m_extraFileName.clear();
       
   613             m_lineNumber = -1;
       
   614         } else {
       
   615             popContext(XC_context_group_any);
       
   616         }
       
   617     } else if (localName == QLatin1String("context")) {
       
   618         if (popContext(XC_context_linenumber)) {
       
   619             bool ok;
       
   620             m_lineNumber = accum.trimmed().toInt(&ok);
       
   621             if (!ok)
       
   622                 m_lineNumber = -1;
       
   623         } else if (popContext(XC_context_filename)) {
       
   624             m_extraFileName = accum;
       
   625         } else if (popContext(XC_context_comment)) {
       
   626             m_comment = accum;
       
   627         } else if (popContext(XC_context_old_comment)) {
       
   628             m_oldComment = accum;
       
   629         }
       
   630     } else if (localName == QLatin1String("note")) {
       
   631         if (popContext(XC_extra_comment))
       
   632             m_extraComment = accum;
       
   633         else if (popContext(XC_translator_comment))
       
   634             m_translatorComment = accum;
       
   635     } else if (localName == QLatin1String("ph")) {
       
   636         m_ctype.clear();
       
   637         popContext(XC_ph);
       
   638     } else if (localName == QLatin1String("trans-unit")) {
       
   639         popContext(XC_trans_unit);
       
   640         if (!m_hadAlt)
       
   641             m_oldSources.append(QString());
       
   642         if (!hasContext(XC_restype_plurals)) {
       
   643             if (!finalizeMessage(false))
       
   644                 return false;
       
   645         }
       
   646     } else if (localName == QLatin1String("alt-trans")) {
       
   647         popContext(XC_alt_trans);
       
   648     } else if (localName == QLatin1String("group")) {
       
   649         if (popContext(XC_restype_plurals)) {
       
   650             if (!finalizeMessage(true))
       
   651                 return false;
       
   652         } else if (popContext(XC_restype_context)) {
       
   653             m_context.clear();
       
   654         } else {
       
   655             popContext(XC_group);
       
   656         }
       
   657     }
       
   658     return true;
       
   659 }
       
   660 
       
   661 bool XLIFFHandler::characters(const QString &ch)
       
   662 {
       
   663     if (currentContext() == XC_ph) {
       
   664         // handle the content of <ph> elements
       
   665         for (int i = 0; i < ch.count(); ++i) {
       
   666             QChar chr = ch.at(i);
       
   667             if (accum.endsWith(QLatin1Char('\\')))
       
   668                 accum[accum.size() - 1] = QLatin1Char(charFromEscape(chr.toAscii()));
       
   669             else
       
   670                 accum.append(chr);
       
   671         }
       
   672     } else {
       
   673         QString t = ch;
       
   674         t.replace(QLatin1String("\r"), QLatin1String(""));
       
   675         accum.append(t);
       
   676     }
       
   677     return true;
       
   678 }
       
   679 
       
   680 bool XLIFFHandler::endDocument()
       
   681 {
       
   682     m_translator.setLanguageCode(m_language);
       
   683     m_translator.setSourceLanguageCode(m_sourceLanguage);
       
   684     return true;
       
   685 }
       
   686 
       
   687 bool XLIFFHandler::finalizeMessage(bool isPlural)
       
   688 {
       
   689     if (m_sources.isEmpty()) {
       
   690         m_cd.appendError(QLatin1String("XLIFF syntax error: Message without source string."));
       
   691         return false;
       
   692     }
       
   693     TranslatorMessage msg(m_context, m_sources[0],
       
   694                           m_comment, QString(), QString(), -1,
       
   695                           m_translations, m_type, isPlural);
       
   696     msg.setId(m_id);
       
   697     msg.setReferences(m_refs);
       
   698     msg.setOldComment(m_oldComment);
       
   699     msg.setExtraComment(m_extraComment);
       
   700     msg.setTranslatorComment(m_translatorComment);
       
   701     if (m_sources.count() > 1 && m_sources[1] != m_sources[0])
       
   702         m_extra.insert(QLatin1String("po-msgid_plural"), m_sources[1]);
       
   703     if (!m_oldSources.isEmpty()) {
       
   704         if (!m_oldSources[0].isEmpty())
       
   705             msg.setOldSourceText(m_oldSources[0]);
       
   706         if (m_oldSources.count() > 1 && m_oldSources[1] != m_oldSources[0])
       
   707             m_extra.insert(QLatin1String("po-old_msgid_plural"), m_oldSources[1]);
       
   708     }
       
   709     msg.setExtras(m_extra);
       
   710     m_translator.append(msg);
       
   711 
       
   712     m_id.clear();
       
   713     m_sources.clear();
       
   714     m_oldSources.clear();
       
   715     m_translations.clear();
       
   716     m_comment.clear();
       
   717     m_oldComment.clear();
       
   718     m_extraComment.clear();
       
   719     m_translatorComment.clear();
       
   720     m_extra.clear();
       
   721     m_refs.clear();
       
   722     m_type = TranslatorMessage::Finished;
       
   723     return true;
       
   724 }
       
   725 
       
   726 bool XLIFFHandler::fatalError(const QXmlParseException &exception)
       
   727 {
       
   728     QString msg;
       
   729     msg.sprintf("XML error: Parse error at line %d, column %d (%s).\n",
       
   730                  exception.lineNumber(), exception.columnNumber(),
       
   731                  exception.message().toLatin1().data() );
       
   732     m_cd.appendError(msg);
       
   733     return false;
       
   734 }
       
   735 
       
   736 bool loadXLIFF(Translator &translator, QIODevice &dev, ConversionData &cd)
       
   737 {
       
   738     QXmlInputSource in(&dev);
       
   739     QXmlSimpleReader reader;
       
   740     XLIFFHandler hand(translator, cd);
       
   741     reader.setContentHandler(&hand);
       
   742     reader.setErrorHandler(&hand);
       
   743     return reader.parse(in);
       
   744 }
       
   745 
       
   746 bool saveXLIFF(const Translator &translator, QIODevice &dev, ConversionData &cd)
       
   747 {
       
   748     bool ok = true;
       
   749     int indent = 0;
       
   750 
       
   751     QTextStream ts(&dev);
       
   752     ts.setCodec(QTextCodec::codecForName("UTF-8"));
       
   753 
       
   754     QStringList dtgs = cd.dropTags();
       
   755     dtgs << QLatin1String("po-(old_)?msgid_plural");
       
   756     QRegExp drops(dtgs.join(QLatin1String("|")));
       
   757 
       
   758     QHash<QString, QHash<QString, QList<TranslatorMessage> > > messageOrder;
       
   759     QHash<QString, QList<QString> > contextOrder;
       
   760     QList<QString> fileOrder;
       
   761     foreach (const TranslatorMessage &msg, translator.messages()) {
       
   762         QHash<QString, QList<TranslatorMessage> > &file = messageOrder[msg.fileName()];
       
   763         if (file.isEmpty())
       
   764             fileOrder.append(msg.fileName());
       
   765         QList<TranslatorMessage> &context = file[msg.context()];
       
   766         if (context.isEmpty())
       
   767             contextOrder[msg.fileName()].append(msg.context());
       
   768         context.append(msg);
       
   769     }
       
   770 
       
   771     ts.setFieldAlignment(QTextStream::AlignRight);
       
   772     ts << "<?xml version=\"1.0\"";
       
   773     ts << " encoding=\"utf-8\"?>\n";
       
   774     ts << "<xliff version=\"1.2\" xmlns=\"" << XLIFF12namespaceURI
       
   775        << "\" xmlns:trolltech=\"" << TrollTsNamespaceURI << "\">\n";
       
   776     ++indent;
       
   777     writeExtras(ts, indent, translator.extras(), drops);
       
   778     QString sourceLanguageCode = translator.sourceLanguageCode();
       
   779     if (sourceLanguageCode.isEmpty() || sourceLanguageCode == QLatin1String("C"))
       
   780         sourceLanguageCode = QLatin1String("en");
       
   781     else
       
   782         sourceLanguageCode.replace(QLatin1Char('_'), QLatin1Char('-'));
       
   783     QString languageCode = translator.languageCode();
       
   784     languageCode.replace(QLatin1Char('_'), QLatin1Char('-'));
       
   785     foreach (const QString &fn, fileOrder) {
       
   786         writeIndent(ts, indent);
       
   787         ts << "<file original=\"" << fn << "\""
       
   788             << " datatype=\"" << dataType(messageOrder[fn].begin()->first()) << "\""
       
   789             << " source-language=\"" << sourceLanguageCode.toLatin1() << "\""
       
   790             << " target-language=\"" << languageCode.toLatin1() << "\""
       
   791             << "><body>\n";
       
   792         ++indent;
       
   793 
       
   794         foreach (const QString &ctx, contextOrder[fn]) {
       
   795             if (!ctx.isEmpty()) {
       
   796                 writeIndent(ts, indent);
       
   797                 ts << "<group restype=\"" << restypeContext << "\""
       
   798                     << " resname=\"" << protect(ctx) << "\">\n";
       
   799                 ++indent;
       
   800             }
       
   801 
       
   802             foreach (const TranslatorMessage &msg, messageOrder[fn][ctx])
       
   803                 writeMessage(ts, msg, drops, indent);
       
   804 
       
   805             if (!ctx.isEmpty()) {
       
   806                 --indent;
       
   807                 writeIndent(ts, indent);
       
   808                 ts << "</group>\n";
       
   809             }
       
   810         }
       
   811 
       
   812         --indent;
       
   813         writeIndent(ts, indent);
       
   814         ts << "</body></file>\n";
       
   815     }
       
   816     --indent;
       
   817     writeIndent(ts, indent);
       
   818     ts << "</xliff>\n";
       
   819 
       
   820     return ok;
       
   821 }
       
   822 
       
   823 int initXLIFF()
       
   824 {
       
   825     Translator::FileFormat format;
       
   826     format.extension = QLatin1String("xlf");
       
   827     format.description = QObject::tr("XLIFF localization files");
       
   828     format.fileType = Translator::FileFormat::TranslationSource;
       
   829     format.priority = 1;
       
   830     format.loader = &loadXLIFF;
       
   831     format.saver = &saveXLIFF;
       
   832     Translator::registerFileFormat(format);
       
   833     return 1;
       
   834 }
       
   835 
       
   836 Q_CONSTRUCTOR_FUNCTION(initXLIFF)
       
   837 
       
   838 QT_END_NAMESPACE