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

Last change on this file since 769 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 29.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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** 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**
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** 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 have questions regarding the use of this file, please contact
37** Nokia at qt-info@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{
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
351static 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
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_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 }
571bail:
572 if (currentContext() != XC_ph)
573 accum.clear();
574 return true;
575}
576
577bool 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
661bool 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
680bool XLIFFHandler::endDocument()
681{
682 m_translator.setLanguageCode(m_language);
683 m_translator.setSourceLanguageCode(m_sourceLanguage);
684 return true;
685}
686
687bool 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
726bool 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
736bool 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
746bool 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
823int 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
836Q_CONSTRUCTOR_FUNCTION(initXLIFF)
837
838QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.