source: branches/vendor/trolltech/qt/current/tools/linguist/shared/xliff.cpp @ 2

Last change on this file since 2 was 2, checked in by Dmitry A. Kuminov, 13 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 28.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file.  Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file.  Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at qt-sales@nokia.com.
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
56QT_BEGIN_NAMESPACE
57
58/**
59 * Implementation of XLIFF file format for Linguist
60 */
61//static const char *restypeDomain = "x-gettext-domain";
62static const char *restypeContext = "x-trolltech-linguist-context";
63static const char *restypePlurals = "x-gettext-plurals";
64static const char *restypeDummy = "x-dummy";
65static const char *dataTypeUIFile = "x-trolltech-designer-ui";
66static const char *contextMsgctxt = "x-gettext-msgctxt"; // XXX Troll invention, so far.
67static const char *contextOldMsgctxt = "x-gettext-previous-msgctxt"; // XXX Troll invention, so far.
68static const char *attribPlural = "trolltech:plural";
69static const char *XLIFF11namespaceURI = "urn:oasis:names:tc:xliff:document:1.1";
70static const char *XLIFF12namespaceURI = "urn:oasis:names:tc:xliff:document:1.2";
71static 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
76static 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
108static void writeIndent(QTextStream &ts, int indent)
109{
110    ts << QString().fill(QLatin1Char(' '), indent * 2);
111}
112
113struct CharMnemonic
114{
115    char ch;
116    char escape;
117    const char *mnemonic;
118};
119
120static 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
130static 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
141static 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
156static 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
189static 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
202static 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
219static 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
246static void writeTransUnits(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent,
247                            const Translator &translator, ConversionData &cd, bool *ok)
248{
249    static int msgid;
250    QString msgidstr = !msg.id().isEmpty() ? msg.id() : QString::fromAscii("_msg%1").arg(++msgid);
251
252    QStringList translns = translator.normalizedTranslations(msg, cd, ok);
253    QHash<QString, QString>::const_iterator it;
254    QString pluralStr;
255    QStringList sources(msg.sourceText());
256    if ((it = msg.extras().find(QString::fromLatin1("po-msgid_plural"))) != msg.extras().end())
257        sources.append(*it);
258    QStringList oldsources;
259    if (!msg.oldSourceText().isEmpty())
260        oldsources.append(msg.oldSourceText());
261    if ((it = msg.extras().find(QString::fromLatin1("po-old_msgid_plural"))) != msg.extras().end()) {
262        if (oldsources.isEmpty()) {
263            if (sources.count() == 2)
264                oldsources.append(QString());
265            else
266                pluralStr = QLatin1Char(' ') + QLatin1String(attribPlural) + QLatin1String("=\"yes\"");
267        }
268        oldsources.append(*it);
269    }
270
271    QStringList::const_iterator
272        srcit = sources.begin(), srcend = sources.end(),
273        oldsrcit = oldsources.begin(), oldsrcend = oldsources.end(),
274        transit = translns.begin(), transend = translns.end();
275    int plural = 0;
276    QString source;
277    while (srcit != srcend || oldsrcit != oldsrcend || transit != transend) {
278        QByteArray attribs;
279        QByteArray state;
280        if (msg.type() == TranslatorMessage::Obsolete) {
281            if (!msg.isPlural())
282                attribs = " translate=\"no\"";
283        } else if (msg.type() == TranslatorMessage::Finished) {
284            attribs = " approved=\"yes\"";
285        } else if (transit != transend && !transit->isEmpty()) {
286            state = " state=\"needs-review-translation\"";
287        }
288        writeIndent(ts, indent);
289        ts << "<trans-unit id=\"" << msgidstr;
290        if (msg.isPlural())
291            ts << "[" << plural++ << "]";
292        ts << "\"" << attribs << ">\n";
293        ++indent;
294
295        writeIndent(ts, indent);
296        if (srcit != srcend) {
297            source = *srcit;
298            ++srcit;
299        } // else just repeat last element
300        ts << "<source xml:space=\"preserve\">" << protect(source) << "</source>\n";
301
302        bool puttrans = false;
303        QString translation;
304        if (transit != transend) {
305            translation = *transit;
306            ++transit;
307            puttrans = true;
308        }
309        do {
310            if (oldsrcit != oldsrcend && !oldsrcit->isEmpty()) {
311                writeIndent(ts, indent);
312                ts << "<alt-trans>\n";
313                ++indent;
314                writeIndent(ts, indent);
315                ts << "<source xml:space=\"preserve\"" << pluralStr << '>' << protect(*oldsrcit) << "</source>\n";
316                if (!puttrans) {
317                    writeIndent(ts, indent);
318                    ts << "<target restype=\"" << restypeDummy << "\"/>\n";
319                }
320            }
321
322            if (puttrans) {
323                writeIndent(ts, indent);
324                ts << "<target xml:space=\"preserve\"" << state << ">" << protect(translation) << "</target>\n";
325            }
326
327            if (oldsrcit != oldsrcend) {
328                if (!oldsrcit->isEmpty()) {
329                    --indent;
330                    writeIndent(ts, indent);
331                    ts << "</alt-trans>\n";
332                }
333                ++oldsrcit;
334            }
335
336            puttrans = false;
337        } while (srcit == srcend && oldsrcit != oldsrcend);
338
339        if (!msg.isPlural()) {
340            writeLineNumber(ts, msg, indent);
341            writeComment(ts, msg, drops, indent);
342        }
343
344        --indent;
345        writeIndent(ts, indent);
346        ts << "</trans-unit>\n";
347    }
348}
349
350static void writeMessage(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent,
351                         const Translator &translator, ConversionData &cd, bool *ok)
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, translator, cd, ok);
366        --indent;
367        writeIndent(ts, indent);
368        ts << "</group>\n";
369    } else {
370        writeTransUnits(ts, msg, drops, indent, translator, cd, ok);
371    }
372}
373
374
375class XLIFFHandler : public QXmlDefaultHandler
376{
377public:
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
389private:
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
416private:
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
447XLIFFHandler::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
457void 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
463bool 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
472XLIFFHandler::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.
480bool 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
489bool 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_sourceLanguage = atts.value(QLatin1String("source-language"));
504    } else if (localName == QLatin1String("group")) {
505        if (atts.value(QLatin1String("restype")) == QLatin1String(restypeContext)) {
506            m_context = atts.value(QLatin1String("resname"));
507            pushContext(XC_restype_context);
508        } else {
509            if (atts.value(QLatin1String("restype")) == QLatin1String(restypePlurals)) {
510                pushContext(XC_restype_plurals);
511                m_id = atts.value(QLatin1String("id"));
512                if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
513                    m_type = TranslatorMessage::Obsolete;
514            } else {
515                pushContext(XC_group);
516            }
517        }
518    } else if (localName == QLatin1String("trans-unit")) {
519        if (!hasContext(XC_restype_plurals) || m_sources.isEmpty() /* who knows ... */)
520            if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
521                m_type = TranslatorMessage::Obsolete;
522        if (!hasContext(XC_restype_plurals)) {
523            m_id = atts.value(QLatin1String("id"));
524            if (m_id.startsWith(QLatin1String("_msg")))
525                m_id.clear();
526        }
527        if (m_type != TranslatorMessage::Obsolete &&
528            atts.value(QLatin1String("approved")) != QLatin1String("yes"))
529            m_type = TranslatorMessage::Unfinished;
530        pushContext(XC_trans_unit);
531        m_hadAlt = false;
532    } else if (localName == QLatin1String("alt-trans")) {
533        pushContext(XC_alt_trans);
534    } else if (localName == QLatin1String("source")) {
535        m_isPlural = atts.value(QLatin1String(attribPlural)) == QLatin1String("yes");
536    } else if (localName == QLatin1String("target")) {
537        if (atts.value(QLatin1String("restype")) != QLatin1String(restypeDummy))
538            pushContext(XC_restype_translation);
539    } else if (localName == QLatin1String("context-group")) {
540        QString purpose = atts.value(QLatin1String("purpose"));
541        if (purpose == QLatin1String("location"))
542            pushContext(XC_context_group);
543        else
544            pushContext(XC_context_group_any);
545    } else if (currentContext() == XC_context_group && localName == QLatin1String("context")) {
546        QString ctxtype = atts.value(QLatin1String("context-type"));
547        if (ctxtype == QLatin1String("linenumber"))
548            pushContext(XC_context_linenumber);
549        else if (ctxtype == QLatin1String("sourcefile"))
550            pushContext(XC_context_filename);
551    } else if (currentContext() == XC_context_group_any && localName == QLatin1String("context")) {
552        QString ctxtype = atts.value(QLatin1String("context-type"));
553        if (ctxtype == QLatin1String(contextMsgctxt))
554            pushContext(XC_context_comment);
555        else if (ctxtype == QLatin1String(contextOldMsgctxt))
556            pushContext(XC_context_old_comment);
557    } else if (localName == QLatin1String("note")) {
558        if (atts.value(QLatin1String("annotates")) == QLatin1String("source") &&
559            atts.value(QLatin1String("from")) == QLatin1String("developer"))
560            pushContext(XC_extra_comment);
561        else
562            pushContext(XC_translator_comment);
563    } else if (localName == QLatin1String("ph")) {
564        QString ctype = atts.value(QLatin1String("ctype"));
565        if (ctype.startsWith(QLatin1String("x-ch-")))
566            m_ctype = ctype.mid(5);
567        pushContext(XC_ph);
568    }
569bail:
570    if (currentContext() != XC_ph)
571        accum.clear();
572    return true;
573}
574
575bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localName,
576    const QString &qName)
577{
578    Q_UNUSED(qName);
579    if (namespaceURI == m_URITT) {
580        if (hasContext(XC_trans_unit) || hasContext(XC_restype_plurals))
581            m_extra[localName] = accum;
582        else
583            m_translator.setExtra(localName, accum);
584        return true;
585    }
586    if (namespaceURI != m_URI && namespaceURI != m_URI12)
587        return false;
588    //qDebug() << "URI:" <<  namespaceURI << "QNAME:" << qName;
589    if (localName == QLatin1String("xliff")) {
590        popContext(XC_xliff);
591    } else if (localName == QLatin1String("source")) {
592        if (hasContext(XC_alt_trans)) {
593            if (m_isPlural && m_oldSources.isEmpty())
594                m_oldSources.append(QString());
595            m_oldSources.append(accum);
596            m_hadAlt = true;
597        } else {
598            m_sources.append(accum);
599        }
600    } else if (localName == QLatin1String("target")) {
601        if (popContext(XC_restype_translation))
602            m_translations.append(accum);
603    } else if (localName == QLatin1String("context-group")) {
604        if (popContext(XC_context_group)) {
605            m_refs.append(TranslatorMessage::Reference(
606                m_extraFileName.isEmpty() ? m_fileName : m_extraFileName, m_lineNumber));
607            m_extraFileName.clear();
608            m_lineNumber = -1;
609        } else {
610            popContext(XC_context_group_any);
611        }
612    } else if (localName == QLatin1String("context")) {
613        if (popContext(XC_context_linenumber)) {
614            bool ok;
615            m_lineNumber = accum.trimmed().toInt(&ok);
616            if (!ok)
617                m_lineNumber = -1;
618        } else if (popContext(XC_context_filename)) {
619            m_extraFileName = accum;
620        } else if (popContext(XC_context_comment)) {
621            m_comment = accum;
622        } else if (popContext(XC_context_old_comment)) {
623            m_oldComment = accum;
624        }
625    } else if (localName == QLatin1String("note")) {
626        if (popContext(XC_extra_comment))
627            m_extraComment = accum;
628        else if (popContext(XC_translator_comment))
629            m_translatorComment = accum;
630    } else if (localName == QLatin1String("ph")) {
631        m_ctype.clear();
632        popContext(XC_ph);
633    } else if (localName == QLatin1String("trans-unit")) {
634        popContext(XC_trans_unit);
635        if (!m_hadAlt)
636            m_oldSources.append(QString());
637        if (!hasContext(XC_restype_plurals)) {
638            if (!finalizeMessage(false))
639                return false;
640        }
641    } else if (localName == QLatin1String("alt-trans")) {
642        popContext(XC_alt_trans);
643    } else if (localName == QLatin1String("group")) {
644        if (popContext(XC_restype_plurals)) {
645            if (!finalizeMessage(true))
646                return false;
647        } else if (popContext(XC_restype_context)) {
648            m_context.clear();
649        } else {
650            popContext(XC_group);
651        }
652    }
653    return true;
654}
655
656bool XLIFFHandler::characters(const QString &ch)
657{
658    if (currentContext() == XC_ph) {
659        // handle the content of <ph> elements
660        for (int i = 0; i < ch.count(); ++i) {
661            QChar chr = ch.at(i);
662            if (accum.endsWith(QLatin1Char('\\')))
663                accum[accum.size() - 1] = QLatin1Char(charFromEscape(chr.toAscii()));
664            else
665                accum.append(chr);
666        }
667    } else {
668        QString t = ch;
669        t.replace(QLatin1String("\r"), QLatin1String(""));
670        accum.append(t);
671    }
672    return true;
673}
674
675bool XLIFFHandler::endDocument()
676{
677    m_translator.setLanguageCode(m_language);
678    m_translator.setSourceLanguageCode(m_sourceLanguage);
679    return true;
680}
681
682bool XLIFFHandler::finalizeMessage(bool isPlural)
683{
684    if (m_sources.isEmpty()) {
685        m_cd.appendError(QLatin1String("XLIFF syntax error: Message without source string."));
686        return false;
687    }
688    TranslatorMessage msg(m_context, m_sources[0],
689                          m_comment, QString(), QString(), -1,
690                          m_translations, m_type, isPlural);
691    msg.setId(m_id);
692    msg.setReferences(m_refs);
693    msg.setOldComment(m_oldComment);
694    msg.setExtraComment(m_extraComment);
695    msg.setTranslatorComment(m_translatorComment);
696    if (m_sources.count() > 1 && m_sources[1] != m_sources[0])
697        m_extra.insert(QLatin1String("po-msgid_plural"), m_sources[1]);
698    if (!m_oldSources.isEmpty()) {
699        if (!m_oldSources[0].isEmpty())
700            msg.setOldSourceText(m_oldSources[0]);
701        if (m_oldSources.count() > 1 && m_oldSources[1] != m_oldSources[0])
702            m_extra.insert(QLatin1String("po-old_msgid_plural"), m_oldSources[1]);
703    }
704    msg.setExtras(m_extra);
705    m_translator.append(msg);
706
707    m_id.clear();
708    m_sources.clear();
709    m_oldSources.clear();
710    m_translations.clear();
711    m_comment.clear();
712    m_oldComment.clear();
713    m_extraComment.clear();
714    m_translatorComment.clear();
715    m_extra.clear();
716    m_refs.clear();
717    m_type = TranslatorMessage::Finished;
718    return true;
719}
720
721bool XLIFFHandler::fatalError(const QXmlParseException &exception)
722{
723    QString msg;
724    msg.sprintf("XML error: Parse error at line %d, column %d (%s).\n",
725                 exception.lineNumber(), exception.columnNumber(),
726                 exception.message().toLatin1().data() );
727    m_cd.appendError(msg);
728    return false;
729}
730
731bool loadXLIFF(Translator &translator, QIODevice &dev, ConversionData &cd)
732{
733    QXmlInputSource in(&dev);
734    QXmlSimpleReader reader;
735    XLIFFHandler hand(translator, cd);
736    reader.setContentHandler(&hand);
737    reader.setErrorHandler(&hand);
738    return reader.parse(in);
739}
740
741bool saveXLIFF(const Translator &translator, QIODevice &dev, ConversionData &cd)
742{
743    bool ok = true;
744    int indent = 0;
745
746    QTextStream ts(&dev);
747    ts.setCodec(QTextCodec::codecForName("UTF-8"));
748
749    QStringList dtgs = cd.dropTags();
750    dtgs << QLatin1String("po-(old_)?msgid_plural");
751    QRegExp drops(dtgs.join(QLatin1String("|")));
752
753    QHash<QString, QHash<QString, QList<TranslatorMessage> > > messageOrder;
754    QHash<QString, QList<QString> > contextOrder;
755    QList<QString> fileOrder;
756    foreach (const TranslatorMessage &msg, translator.messages()) {
757        QHash<QString, QList<TranslatorMessage> > &file = messageOrder[msg.fileName()];
758        if (file.isEmpty())
759            fileOrder.append(msg.fileName());
760        QList<TranslatorMessage> &context = file[msg.context()];
761        if (context.isEmpty())
762            contextOrder[msg.fileName()].append(msg.context());
763        context.append(msg);
764    }
765
766    ts.setFieldAlignment(QTextStream::AlignRight);
767    ts << "<?xml version=\"1.0\"";
768    ts << " encoding=\"utf-8\"?>\n";
769    ts << "<xliff version=\"1.2\" xmlns=\"" << XLIFF12namespaceURI
770       << "\" xmlns:trolltech=\"" << TrollTsNamespaceURI << "\">\n";
771    ++indent;
772    writeExtras(ts, indent, translator.extras(), drops);
773    foreach (const QString &fn, fileOrder) {
774        writeIndent(ts, indent);
775        ts << "<file original=\"" << fn << "\""
776            << " datatype=\"" << dataType(messageOrder[fn].begin()->first()) << "\""
777            << " source-language=\""
778                << (translator.sourceLanguageCode().isEmpty() ?
779                    QByteArray("en") : translator.sourceLanguageCode().toLatin1()) << "\""
780            << " target-language=\"" << translator.languageCode() << "\""
781            << "><body>\n";
782        ++indent;
783
784        foreach (const QString &ctx, contextOrder[fn]) {
785            if (!ctx.isEmpty()) {
786                writeIndent(ts, indent);
787                ts << "<group restype=\"" << restypeContext << "\""
788                    << " resname=\"" << protect(ctx) << "\">\n";
789                ++indent;
790            }
791
792            foreach (const TranslatorMessage &msg, messageOrder[fn][ctx])
793                writeMessage(ts, msg, drops, indent, translator, cd, &ok);
794
795            if (!ctx.isEmpty()) {
796                --indent;
797                writeIndent(ts, indent);
798                ts << "</group>\n";
799            }
800        }
801
802        --indent;
803        writeIndent(ts, indent);
804        ts << "</body></file>\n";
805    }
806    --indent;
807    writeIndent(ts, indent);
808    ts << "</xliff>\n";
809
810    return ok;
811}
812
813int initXLIFF()
814{
815    Translator::FileFormat format;
816    format.extension = QLatin1String("xlf");
817    format.description = QObject::tr("XLIFF localization files");
818    format.fileType = Translator::FileFormat::TranslationSource;
819    format.priority = 1;
820    format.loader = &loadXLIFF;
821    format.saver = &saveXLIFF;
822    Translator::registerFileFormat(format);
823    return 1;
824}
825
826Q_CONSTRUCTOR_FUNCTION(initXLIFF)
827
828QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.