source: trunk/tools/linguist/shared/xliff.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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