summaryrefslogtreecommitdiffstats
path: root/src/linguist/shared/xliff.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/linguist/shared/xliff.cpp')
-rw-r--r--src/linguist/shared/xliff.cpp851
1 files changed, 851 insertions, 0 deletions
diff --git a/src/linguist/shared/xliff.cpp b/src/linguist/shared/xliff.cpp
new file mode 100644
index 000000000..40fbcabc1
--- /dev/null
+++ b/src/linguist/shared/xliff.cpp
@@ -0,0 +1,851 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Linguist of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "translator.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QMap>
+#include <QtCore/QStack>
+#include <QtCore/QString>
+#include <QtCore/QTextCodec>
+#include <QtCore/QTextStream>
+
+#include <QtXml/QXmlAttributes>
+#include <QtXml/QXmlDefaultHandler>
+#include <QtXml/QXmlParseException>
+
+
+// The string value is historical and reflects the main purpose: Keeping
+// obsolete entries separate from the magic file message (which both have
+// no location information, but typically reside at opposite ends of the file).
+#define MAGIC_OBSOLETE_REFERENCE "Obsolete_PO_entries"
+
+QT_BEGIN_NAMESPACE
+
+/**
+ * Implementation of XLIFF file format for Linguist
+ */
+//static const char *restypeDomain = "x-gettext-domain";
+static const char *restypeContext = "x-trolltech-linguist-context";
+static const char *restypePlurals = "x-gettext-plurals";
+static const char *restypeDummy = "x-dummy";
+static const char *dataTypeUIFile = "x-trolltech-designer-ui";
+static const char *contextMsgctxt = "x-gettext-msgctxt"; // XXX Troll invention, so far.
+static const char *contextOldMsgctxt = "x-gettext-previous-msgctxt"; // XXX Troll invention, so far.
+static const char *attribPlural = "trolltech:plural";
+static const char *XLIFF11namespaceURI = "urn:oasis:names:tc:xliff:document:1.1";
+static const char *XLIFF12namespaceURI = "urn:oasis:names:tc:xliff:document:1.2";
+static const char *TrollTsNamespaceURI = "urn:trolltech:names:ts:document:1.0";
+
+#define COMBINE4CHARS(c1, c2, c3, c4) \
+ (int(c1) << 24 | int(c2) << 16 | int(c3) << 8 | int(c4) )
+
+static QString dataType(const TranslatorMessage &m)
+{
+ QByteArray fileName = m.fileName().toAscii();
+ unsigned int extHash = 0;
+ int pos = fileName.count() - 1;
+ for (int pass = 0; pass < 4 && pos >=0; ++pass, --pos) {
+ if (fileName.at(pos) == '.')
+ break;
+ extHash |= ((int)fileName.at(pos) << (8*pass));
+ }
+
+ switch (extHash) {
+ case COMBINE4CHARS(0,'c','p','p'):
+ case COMBINE4CHARS(0,'c','x','x'):
+ case COMBINE4CHARS(0,'c','+','+'):
+ case COMBINE4CHARS(0,'h','p','p'):
+ case COMBINE4CHARS(0,'h','x','x'):
+ case COMBINE4CHARS(0,'h','+','+'):
+ return QLatin1String("cpp");
+ case COMBINE4CHARS(0, 0 , 0 ,'c'):
+ case COMBINE4CHARS(0, 0 , 0 ,'h'):
+ case COMBINE4CHARS(0, 0 ,'c','c'):
+ case COMBINE4CHARS(0, 0 ,'c','h'):
+ case COMBINE4CHARS(0, 0 ,'h','h'):
+ return QLatin1String("c");
+ case COMBINE4CHARS(0, 0 ,'u','i'):
+ return QLatin1String(dataTypeUIFile); //### form?
+ default:
+ return QLatin1String("plaintext"); // we give up
+ }
+}
+
+static void writeIndent(QTextStream &ts, int indent)
+{
+ ts << QString().fill(QLatin1Char(' '), indent * 2);
+}
+
+struct CharMnemonic
+{
+ char ch;
+ char escape;
+ const char *mnemonic;
+};
+
+static const CharMnemonic charCodeMnemonics[] = {
+ {0x07, 'a', "bel"},
+ {0x08, 'b', "bs"},
+ {0x09, 't', "tab"},
+ {0x0a, 'n', "lf"},
+ {0x0b, 'v', "vt"},
+ {0x0c, 'f', "ff"},
+ {0x0d, 'r', "cr"}
+};
+
+static char charFromEscape(char escape)
+{
+ for (uint i = 0; i < sizeof(charCodeMnemonics)/sizeof(CharMnemonic); ++i) {
+ CharMnemonic cm = charCodeMnemonics[i];
+ if (cm.escape == escape)
+ return cm.ch;
+ }
+ Q_ASSERT(0);
+ return escape;
+}
+
+static QString numericEntity(int ch, bool makePhs)
+{
+ // ### This needs to be reviewed, to reflect the updated XLIFF-PO spec.
+ if (!makePhs || ch < 7 || ch > 0x0d)
+ return QString::fromAscii("&#x%1;").arg(QString::number(ch, 16));
+
+ CharMnemonic cm = charCodeMnemonics[int(ch) - 7];
+ QString name = QLatin1String(cm.mnemonic);
+ char escapechar = cm.escape;
+
+ static int id = 0;
+ return QString::fromAscii("<ph id=\"ph%1\" ctype=\"x-ch-%2\">\\%3</ph>")
+ .arg(++id) .arg(name) .arg(escapechar);
+}
+
+static QString protect(const QString &str, bool makePhs = true)
+{
+ QString result;
+ int len = str.size();
+ for (int i = 0; i != len; ++i) {
+ uint c = str.at(i).unicode();
+ switch (c) {
+ case '\"':
+ result += QLatin1String("&quot;");
+ break;
+ case '&':
+ result += QLatin1String("&amp;");
+ break;
+ case '>':
+ result += QLatin1String("&gt;");
+ break;
+ case '<':
+ result += QLatin1String("&lt;");
+ break;
+ case '\'':
+ result += QLatin1String("&apos;");
+ break;
+ default:
+ if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
+ result += numericEntity(c, makePhs);
+ else // this also covers surrogates
+ result += QChar(c);
+ }
+ }
+ return result;
+}
+
+
+static void writeExtras(QTextStream &ts, int indent,
+ const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
+{
+ for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
+ if (!drops.exactMatch(it.key())) {
+ writeIndent(ts, indent);
+ ts << "<trolltech:" << it.key() << '>'
+ << protect(it.value())
+ << "</trolltech:" << it.key() << ">\n";
+ }
+ }
+}
+
+static void writeLineNumber(QTextStream &ts, const TranslatorMessage &msg, int indent)
+{
+ if (msg.lineNumber() == -1)
+ return;
+ writeIndent(ts, indent);
+ ts << "<context-group purpose=\"location\"><context context-type=\"linenumber\">"
+ << msg.lineNumber() << "</context></context-group>\n";
+ foreach (const TranslatorMessage::Reference &ref, msg.extraReferences()) {
+ writeIndent(ts, indent);
+ ts << "<context-group purpose=\"location\">";
+ if (ref.fileName() != msg.fileName())
+ ts << "<context context-type=\"sourcefile\">" << ref.fileName() << "</context>";
+ ts << "<context context-type=\"linenumber\">" << ref.lineNumber()
+ << "</context></context-group>\n";
+ }
+}
+
+static void writeComment(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
+{
+ if (!msg.comment().isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<context-group><context context-type=\"" << contextMsgctxt << "\">"
+ << protect(msg.comment(), false)
+ << "</context></context-group>\n";
+ }
+ if (!msg.oldComment().isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<context-group><context context-type=\"" << contextOldMsgctxt << "\">"
+ << protect(msg.oldComment(), false)
+ << "</context></context-group>\n";
+ }
+ writeExtras(ts, indent, msg.extras(), drops);
+ if (!msg.extraComment().isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<note annotates=\"source\" from=\"developer\">"
+ << protect(msg.extraComment()) << "</note>\n";
+ }
+ if (!msg.translatorComment().isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<note from=\"translator\">"
+ << protect(msg.translatorComment()) << "</note>\n";
+ }
+}
+
+static void writeTransUnits(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
+{
+ static int msgid;
+ QString msgidstr = !msg.id().isEmpty() ? msg.id() : QString::fromAscii("_msg%1").arg(++msgid);
+
+ QStringList translns = msg.translations();
+ QHash<QString, QString>::const_iterator it;
+ QString pluralStr;
+ QStringList sources(msg.sourceText());
+ if ((it = msg.extras().find(QString::fromLatin1("po-msgid_plural"))) != msg.extras().end())
+ sources.append(*it);
+ QStringList oldsources;
+ if (!msg.oldSourceText().isEmpty())
+ oldsources.append(msg.oldSourceText());
+ if ((it = msg.extras().find(QString::fromLatin1("po-old_msgid_plural"))) != msg.extras().end()) {
+ if (oldsources.isEmpty()) {
+ if (sources.count() == 2)
+ oldsources.append(QString());
+ else
+ pluralStr = QLatin1Char(' ') + QLatin1String(attribPlural) + QLatin1String("=\"yes\"");
+ }
+ oldsources.append(*it);
+ }
+
+ QStringList::const_iterator
+ srcit = sources.begin(), srcend = sources.end(),
+ oldsrcit = oldsources.begin(), oldsrcend = oldsources.end(),
+ transit = translns.begin(), transend = translns.end();
+ int plural = 0;
+ QString source;
+ while (srcit != srcend || oldsrcit != oldsrcend || transit != transend) {
+ QByteArray attribs;
+ QByteArray state;
+ if (msg.type() == TranslatorMessage::Obsolete) {
+ if (!msg.isPlural())
+ attribs = " translate=\"no\"";
+ } else if (msg.type() == TranslatorMessage::Finished) {
+ attribs = " approved=\"yes\"";
+ } else if (transit != transend && !transit->isEmpty()) {
+ state = " state=\"needs-review-translation\"";
+ }
+ writeIndent(ts, indent);
+ ts << "<trans-unit id=\"" << msgidstr;
+ if (msg.isPlural())
+ ts << "[" << plural++ << "]";
+ ts << "\"" << attribs << ">\n";
+ ++indent;
+
+ writeIndent(ts, indent);
+ if (srcit != srcend) {
+ source = *srcit;
+ ++srcit;
+ } // else just repeat last element
+ ts << "<source xml:space=\"preserve\">" << protect(source) << "</source>\n";
+
+ bool puttrans = false;
+ QString translation;
+ if (transit != transend) {
+ translation = *transit;
+ translation.replace(QChar(Translator::BinaryVariantSeparator),
+ QChar(Translator::TextVariantSeparator));
+ ++transit;
+ puttrans = true;
+ }
+ do {
+ if (oldsrcit != oldsrcend && !oldsrcit->isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<alt-trans>\n";
+ ++indent;
+ writeIndent(ts, indent);
+ ts << "<source xml:space=\"preserve\"" << pluralStr << '>' << protect(*oldsrcit) << "</source>\n";
+ if (!puttrans) {
+ writeIndent(ts, indent);
+ ts << "<target restype=\"" << restypeDummy << "\"/>\n";
+ }
+ }
+
+ if (puttrans) {
+ writeIndent(ts, indent);
+ ts << "<target xml:space=\"preserve\"" << state << ">" << protect(translation) << "</target>\n";
+ }
+
+ if (oldsrcit != oldsrcend) {
+ if (!oldsrcit->isEmpty()) {
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</alt-trans>\n";
+ }
+ ++oldsrcit;
+ }
+
+ puttrans = false;
+ } while (srcit == srcend && oldsrcit != oldsrcend);
+
+ if (!msg.isPlural()) {
+ writeLineNumber(ts, msg, indent);
+ writeComment(ts, msg, drops, indent);
+ }
+
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</trans-unit>\n";
+ }
+}
+
+static void writeMessage(QTextStream &ts, const TranslatorMessage &msg, const QRegExp &drops, int indent)
+{
+ if (msg.isPlural()) {
+ writeIndent(ts, indent);
+ ts << "<group restype=\"" << restypePlurals << "\"";
+ if (!msg.id().isEmpty())
+ ts << " id=\"" << msg.id() << "\"";
+ if (msg.type() == TranslatorMessage::Obsolete)
+ ts << " translate=\"no\"";
+ ts << ">\n";
+ ++indent;
+ writeLineNumber(ts, msg, indent);
+ writeComment(ts, msg, drops, indent);
+
+ writeTransUnits(ts, msg, drops, indent);
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</group>\n";
+ } else {
+ writeTransUnits(ts, msg, drops, indent);
+ }
+}
+
+
+class XLIFFHandler : public QXmlDefaultHandler
+{
+public:
+ XLIFFHandler(Translator &translator, ConversionData &cd);
+
+ bool startElement(const QString& namespaceURI, const QString &localName,
+ const QString &qName, const QXmlAttributes &atts );
+ bool endElement(const QString& namespaceURI, const QString &localName,
+ const QString &qName );
+ bool characters(const QString &ch);
+ bool fatalError(const QXmlParseException &exception);
+
+ bool endDocument();
+
+private:
+ enum XliffContext {
+ XC_xliff,
+ XC_group,
+ XC_trans_unit,
+ XC_context_group,
+ XC_context_group_any,
+ XC_context,
+ XC_context_filename,
+ XC_context_linenumber,
+ XC_context_context,
+ XC_context_comment,
+ XC_context_old_comment,
+ XC_ph,
+ XC_extra_comment,
+ XC_translator_comment,
+ XC_restype_context,
+ XC_restype_translation,
+ XC_restype_plurals,
+ XC_alt_trans
+ };
+ void pushContext(XliffContext ctx);
+ bool popContext(XliffContext ctx);
+ XliffContext currentContext() const;
+ bool hasContext(XliffContext ctx) const;
+ bool finalizeMessage(bool isPlural);
+
+private:
+ Translator &m_translator;
+ ConversionData &m_cd;
+ TranslatorMessage::Type m_type;
+ QString m_language;
+ QString m_sourceLanguage;
+ QString m_context;
+ QString m_id;
+ QStringList m_sources;
+ QStringList m_oldSources;
+ QString m_comment;
+ QString m_oldComment;
+ QString m_extraComment;
+ QString m_translatorComment;
+ bool m_isPlural;
+ bool m_hadAlt;
+ QStringList m_translations;
+ QString m_fileName;
+ int m_lineNumber;
+ QString m_extraFileName;
+ TranslatorMessage::References m_refs;
+ TranslatorMessage::ExtraData m_extra;
+
+ QString accum;
+ QString m_ctype;
+ const QString m_URITT; // convenience and efficiency
+ const QString m_URI; // ...
+ const QString m_URI12; // ...
+ QStack<int> m_contextStack;
+};
+
+XLIFFHandler::XLIFFHandler(Translator &translator, ConversionData &cd)
+ : m_translator(translator), m_cd(cd),
+ m_type(TranslatorMessage::Finished),
+ m_lineNumber(-1),
+ m_URITT(QLatin1String(TrollTsNamespaceURI)),
+ m_URI(QLatin1String(XLIFF11namespaceURI)),
+ m_URI12(QLatin1String(XLIFF12namespaceURI))
+{}
+
+
+void XLIFFHandler::pushContext(XliffContext ctx)
+{
+ m_contextStack.push_back(ctx);
+}
+
+// Only pops it off if the top of the stack contains ctx
+bool XLIFFHandler::popContext(XliffContext ctx)
+{
+ if (!m_contextStack.isEmpty() && m_contextStack.top() == ctx) {
+ m_contextStack.pop();
+ return true;
+ }
+ return false;
+}
+
+XLIFFHandler::XliffContext XLIFFHandler::currentContext() const
+{
+ if (!m_contextStack.isEmpty())
+ return (XliffContext)m_contextStack.top();
+ return XC_xliff;
+}
+
+// traverses to the top to check all of the parent contexes.
+bool XLIFFHandler::hasContext(XliffContext ctx) const
+{
+ for (int i = m_contextStack.count() - 1; i >= 0; --i) {
+ if (m_contextStack.at(i) == ctx)
+ return true;
+ }
+ return false;
+}
+
+bool XLIFFHandler::startElement(const QString& namespaceURI,
+ const QString &localName, const QString &qName, const QXmlAttributes &atts )
+{
+ Q_UNUSED(qName);
+ if (namespaceURI == m_URITT)
+ goto bail;
+ if (namespaceURI != m_URI && namespaceURI != m_URI12)
+ return false;
+ if (localName == QLatin1String("xliff")) {
+ // make sure that the stack is not empty during parsing
+ pushContext(XC_xliff);
+ } else if (localName == QLatin1String("file")) {
+ m_fileName = atts.value(QLatin1String("original"));
+ m_language = atts.value(QLatin1String("target-language"));
+ m_language.replace(QLatin1Char('-'), QLatin1Char('_'));
+ m_sourceLanguage = atts.value(QLatin1String("source-language"));
+ m_sourceLanguage.replace(QLatin1Char('-'), QLatin1Char('_'));
+ if (m_sourceLanguage == QLatin1String("en"))
+ m_sourceLanguage.clear();
+ } else if (localName == QLatin1String("group")) {
+ if (atts.value(QLatin1String("restype")) == QLatin1String(restypeContext)) {
+ m_context = atts.value(QLatin1String("resname"));
+ pushContext(XC_restype_context);
+ } else {
+ if (atts.value(QLatin1String("restype")) == QLatin1String(restypePlurals)) {
+ pushContext(XC_restype_plurals);
+ m_id = atts.value(QLatin1String("id"));
+ if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
+ m_type = TranslatorMessage::Obsolete;
+ } else {
+ pushContext(XC_group);
+ }
+ }
+ } else if (localName == QLatin1String("trans-unit")) {
+ if (!hasContext(XC_restype_plurals) || m_sources.isEmpty() /* who knows ... */)
+ if (atts.value(QLatin1String("translate")) == QLatin1String("no"))
+ m_type = TranslatorMessage::Obsolete;
+ if (!hasContext(XC_restype_plurals)) {
+ m_id = atts.value(QLatin1String("id"));
+ if (m_id.startsWith(QLatin1String("_msg")))
+ m_id.clear();
+ }
+ if (m_type != TranslatorMessage::Obsolete &&
+ atts.value(QLatin1String("approved")) != QLatin1String("yes"))
+ m_type = TranslatorMessage::Unfinished;
+ pushContext(XC_trans_unit);
+ m_hadAlt = false;
+ } else if (localName == QLatin1String("alt-trans")) {
+ pushContext(XC_alt_trans);
+ } else if (localName == QLatin1String("source")) {
+ m_isPlural = atts.value(QLatin1String(attribPlural)) == QLatin1String("yes");
+ } else if (localName == QLatin1String("target")) {
+ if (atts.value(QLatin1String("restype")) != QLatin1String(restypeDummy))
+ pushContext(XC_restype_translation);
+ } else if (localName == QLatin1String("context-group")) {
+ QString purpose = atts.value(QLatin1String("purpose"));
+ if (purpose == QLatin1String("location"))
+ pushContext(XC_context_group);
+ else
+ pushContext(XC_context_group_any);
+ } else if (currentContext() == XC_context_group && localName == QLatin1String("context")) {
+ QString ctxtype = atts.value(QLatin1String("context-type"));
+ if (ctxtype == QLatin1String("linenumber"))
+ pushContext(XC_context_linenumber);
+ else if (ctxtype == QLatin1String("sourcefile"))
+ pushContext(XC_context_filename);
+ } else if (currentContext() == XC_context_group_any && localName == QLatin1String("context")) {
+ QString ctxtype = atts.value(QLatin1String("context-type"));
+ if (ctxtype == QLatin1String(contextMsgctxt))
+ pushContext(XC_context_comment);
+ else if (ctxtype == QLatin1String(contextOldMsgctxt))
+ pushContext(XC_context_old_comment);
+ } else if (localName == QLatin1String("note")) {
+ if (atts.value(QLatin1String("annotates")) == QLatin1String("source") &&
+ atts.value(QLatin1String("from")) == QLatin1String("developer"))
+ pushContext(XC_extra_comment);
+ else
+ pushContext(XC_translator_comment);
+ } else if (localName == QLatin1String("ph")) {
+ QString ctype = atts.value(QLatin1String("ctype"));
+ if (ctype.startsWith(QLatin1String("x-ch-")))
+ m_ctype = ctype.mid(5);
+ pushContext(XC_ph);
+ }
+bail:
+ if (currentContext() != XC_ph)
+ accum.clear();
+ return true;
+}
+
+bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localName,
+ const QString &qName)
+{
+ Q_UNUSED(qName);
+ if (namespaceURI == m_URITT) {
+ if (hasContext(XC_trans_unit) || hasContext(XC_restype_plurals))
+ m_extra[localName] = accum;
+ else
+ m_translator.setExtra(localName, accum);
+ return true;
+ }
+ if (namespaceURI != m_URI && namespaceURI != m_URI12)
+ return false;
+ //qDebug() << "URI:" << namespaceURI << "QNAME:" << qName;
+ if (localName == QLatin1String("xliff")) {
+ popContext(XC_xliff);
+ } else if (localName == QLatin1String("source")) {
+ if (hasContext(XC_alt_trans)) {
+ if (m_isPlural && m_oldSources.isEmpty())
+ m_oldSources.append(QString());
+ m_oldSources.append(accum);
+ m_hadAlt = true;
+ } else {
+ m_sources.append(accum);
+ }
+ } else if (localName == QLatin1String("target")) {
+ if (popContext(XC_restype_translation)) {
+ accum.replace(QChar(Translator::TextVariantSeparator),
+ QChar(Translator::BinaryVariantSeparator));
+ m_translations.append(accum);
+ }
+ } else if (localName == QLatin1String("context-group")) {
+ if (popContext(XC_context_group)) {
+ m_refs.append(TranslatorMessage::Reference(
+ m_extraFileName.isEmpty() ? m_fileName : m_extraFileName, m_lineNumber));
+ m_extraFileName.clear();
+ m_lineNumber = -1;
+ } else {
+ popContext(XC_context_group_any);
+ }
+ } else if (localName == QLatin1String("context")) {
+ if (popContext(XC_context_linenumber)) {
+ bool ok;
+ m_lineNumber = accum.trimmed().toInt(&ok);
+ if (!ok)
+ m_lineNumber = -1;
+ } else if (popContext(XC_context_filename)) {
+ m_extraFileName = accum;
+ } else if (popContext(XC_context_comment)) {
+ m_comment = accum;
+ } else if (popContext(XC_context_old_comment)) {
+ m_oldComment = accum;
+ }
+ } else if (localName == QLatin1String("note")) {
+ if (popContext(XC_extra_comment))
+ m_extraComment = accum;
+ else if (popContext(XC_translator_comment))
+ m_translatorComment = accum;
+ } else if (localName == QLatin1String("ph")) {
+ m_ctype.clear();
+ popContext(XC_ph);
+ } else if (localName == QLatin1String("trans-unit")) {
+ popContext(XC_trans_unit);
+ if (!m_hadAlt)
+ m_oldSources.append(QString());
+ if (!hasContext(XC_restype_plurals)) {
+ if (!finalizeMessage(false))
+ return false;
+ }
+ } else if (localName == QLatin1String("alt-trans")) {
+ popContext(XC_alt_trans);
+ } else if (localName == QLatin1String("group")) {
+ if (popContext(XC_restype_plurals)) {
+ if (!finalizeMessage(true))
+ return false;
+ } else if (popContext(XC_restype_context)) {
+ m_context.clear();
+ } else {
+ popContext(XC_group);
+ }
+ }
+ return true;
+}
+
+bool XLIFFHandler::characters(const QString &ch)
+{
+ if (currentContext() == XC_ph) {
+ // handle the content of <ph> elements
+ for (int i = 0; i < ch.count(); ++i) {
+ QChar chr = ch.at(i);
+ if (accum.endsWith(QLatin1Char('\\')))
+ accum[accum.size() - 1] = QLatin1Char(charFromEscape(chr.toAscii()));
+ else
+ accum.append(chr);
+ }
+ } else {
+ QString t = ch;
+ t.replace(QLatin1String("\r"), QLatin1String(""));
+ accum.append(t);
+ }
+ return true;
+}
+
+bool XLIFFHandler::endDocument()
+{
+ m_translator.setLanguageCode(m_language);
+ m_translator.setSourceLanguageCode(m_sourceLanguage);
+ return true;
+}
+
+bool XLIFFHandler::finalizeMessage(bool isPlural)
+{
+ if (m_sources.isEmpty()) {
+ m_cd.appendError(QLatin1String("XLIFF syntax error: Message without source string."));
+ return false;
+ }
+ if (m_type == TranslatorMessage::Obsolete && m_refs.size() == 1
+ && m_refs.at(0).fileName() == QLatin1String(MAGIC_OBSOLETE_REFERENCE))
+ m_refs.clear();
+ TranslatorMessage msg(m_context, m_sources[0],
+ m_comment, QString(), QString(), -1,
+ m_translations, m_type, isPlural);
+ msg.setId(m_id);
+ msg.setReferences(m_refs);
+ msg.setOldComment(m_oldComment);
+ msg.setExtraComment(m_extraComment);
+ msg.setTranslatorComment(m_translatorComment);
+ if (m_sources.count() > 1 && m_sources[1] != m_sources[0])
+ m_extra.insert(QLatin1String("po-msgid_plural"), m_sources[1]);
+ if (!m_oldSources.isEmpty()) {
+ if (!m_oldSources[0].isEmpty())
+ msg.setOldSourceText(m_oldSources[0]);
+ if (m_oldSources.count() > 1 && m_oldSources[1] != m_oldSources[0])
+ m_extra.insert(QLatin1String("po-old_msgid_plural"), m_oldSources[1]);
+ }
+ msg.setExtras(m_extra);
+ m_translator.append(msg);
+
+ m_id.clear();
+ m_sources.clear();
+ m_oldSources.clear();
+ m_translations.clear();
+ m_comment.clear();
+ m_oldComment.clear();
+ m_extraComment.clear();
+ m_translatorComment.clear();
+ m_extra.clear();
+ m_refs.clear();
+ m_type = TranslatorMessage::Finished;
+ return true;
+}
+
+bool XLIFFHandler::fatalError(const QXmlParseException &exception)
+{
+ QString msg;
+ msg.sprintf("XML error: Parse error at line %d, column %d (%s).\n",
+ exception.lineNumber(), exception.columnNumber(),
+ exception.message().toLatin1().data() );
+ m_cd.appendError(msg);
+ return false;
+}
+
+bool loadXLIFF(Translator &translator, QIODevice &dev, ConversionData &cd)
+{
+ QXmlInputSource in(&dev);
+ QXmlSimpleReader reader;
+ XLIFFHandler hand(translator, cd);
+ reader.setContentHandler(&hand);
+ reader.setErrorHandler(&hand);
+ return reader.parse(in);
+}
+
+bool saveXLIFF(const Translator &translator, QIODevice &dev, ConversionData &cd)
+{
+ bool ok = true;
+ int indent = 0;
+
+ QTextStream ts(&dev);
+ ts.setCodec(QTextCodec::codecForName("UTF-8"));
+
+ QStringList dtgs = cd.dropTags();
+ dtgs << QLatin1String("po-(old_)?msgid_plural");
+ QRegExp drops(dtgs.join(QLatin1String("|")));
+
+ QHash<QString, QHash<QString, QList<TranslatorMessage> > > messageOrder;
+ QHash<QString, QList<QString> > contextOrder;
+ QList<QString> fileOrder;
+ foreach (const TranslatorMessage &msg, translator.messages()) {
+ QString fn = msg.fileName();
+ if (fn.isEmpty() && msg.type() == TranslatorMessage::Obsolete)
+ fn = QLatin1String(MAGIC_OBSOLETE_REFERENCE);
+ QHash<QString, QList<TranslatorMessage> > &file = messageOrder[fn];
+ if (file.isEmpty())
+ fileOrder.append(fn);
+ QList<TranslatorMessage> &context = file[msg.context()];
+ if (context.isEmpty())
+ contextOrder[fn].append(msg.context());
+ context.append(msg);
+ }
+
+ ts.setFieldAlignment(QTextStream::AlignRight);
+ ts << "<?xml version=\"1.0\"";
+ ts << " encoding=\"utf-8\"?>\n";
+ ts << "<xliff version=\"1.2\" xmlns=\"" << XLIFF12namespaceURI
+ << "\" xmlns:trolltech=\"" << TrollTsNamespaceURI << "\">\n";
+ ++indent;
+ writeExtras(ts, indent, translator.extras(), drops);
+ QString sourceLanguageCode = translator.sourceLanguageCode();
+ if (sourceLanguageCode.isEmpty() || sourceLanguageCode == QLatin1String("C"))
+ sourceLanguageCode = QLatin1String("en");
+ else
+ sourceLanguageCode.replace(QLatin1Char('_'), QLatin1Char('-'));
+ QString languageCode = translator.languageCode();
+ languageCode.replace(QLatin1Char('_'), QLatin1Char('-'));
+ foreach (const QString &fn, fileOrder) {
+ writeIndent(ts, indent);
+ ts << "<file original=\"" << fn << "\""
+ << " datatype=\"" << dataType(messageOrder[fn].begin()->first()) << "\""
+ << " source-language=\"" << sourceLanguageCode.toLatin1() << "\""
+ << " target-language=\"" << languageCode.toLatin1() << "\""
+ << "><body>\n";
+ ++indent;
+
+ foreach (const QString &ctx, contextOrder[fn]) {
+ if (!ctx.isEmpty()) {
+ writeIndent(ts, indent);
+ ts << "<group restype=\"" << restypeContext << "\""
+ << " resname=\"" << protect(ctx) << "\">\n";
+ ++indent;
+ }
+
+ foreach (const TranslatorMessage &msg, messageOrder[fn][ctx])
+ writeMessage(ts, msg, drops, indent);
+
+ if (!ctx.isEmpty()) {
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</group>\n";
+ }
+ }
+
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</body></file>\n";
+ }
+ --indent;
+ writeIndent(ts, indent);
+ ts << "</xliff>\n";
+
+ return ok;
+}
+
+int initXLIFF()
+{
+ Translator::FileFormat format;
+ format.extension = QLatin1String("xlf");
+ format.description = QObject::tr("XLIFF localization files");
+ format.fileType = Translator::FileFormat::TranslationSource;
+ format.priority = 1;
+ format.loader = &loadXLIFF;
+ format.saver = &saveXLIFF;
+ Translator::registerFileFormat(format);
+ return 1;
+}
+
+Q_CONSTRUCTOR_FUNCTION(initXLIFF)
+
+QT_END_NAMESPACE