| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the Qt Linguist of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include "translator.h" |
| 30 | |
| 31 | #include <QtCore/QByteArray> |
| 32 | #include <QtCore/QDebug> |
| 33 | #include <QtCore/QRegExp> |
| 34 | #include <QtCore/QTextCodec> |
| 35 | #include <QtCore/QTextStream> |
| 36 | |
| 37 | #include <QtCore/QXmlStreamReader> |
| 38 | |
| 39 | #include <algorithm> |
| 40 | |
| 41 | #define STRINGIFY_INTERNAL(x) #x |
| 42 | #define STRINGIFY(x) STRINGIFY_INTERNAL(x) |
| 43 | #define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s))) |
| 44 | |
| 45 | QT_BEGIN_NAMESPACE |
| 46 | |
| 47 | QDebug &operator<<(QDebug &d, const QXmlStreamAttribute &attr) |
| 48 | { |
| 49 | return d << "[" << attr.name().toString() << "," << attr.value().toString() << "]" ; |
| 50 | } |
| 51 | |
| 52 | |
| 53 | class TSReader : public QXmlStreamReader |
| 54 | { |
| 55 | public: |
| 56 | TSReader(QIODevice &dev, ConversionData &cd) |
| 57 | : QXmlStreamReader(&dev), m_cd(cd) |
| 58 | {} |
| 59 | |
| 60 | // the "real thing" |
| 61 | bool read(Translator &translator); |
| 62 | |
| 63 | private: |
| 64 | bool elementStarts(const QString &str) const |
| 65 | { |
| 66 | return isStartElement() && name() == str; |
| 67 | } |
| 68 | |
| 69 | bool isWhiteSpace() const |
| 70 | { |
| 71 | return isCharacters() && text().toString().trimmed().isEmpty(); |
| 72 | } |
| 73 | |
| 74 | // needed to expand <byte ... /> |
| 75 | QString readContents(); |
| 76 | // needed to join <lengthvariant>s |
| 77 | QString readTransContents(); |
| 78 | |
| 79 | void handleError(); |
| 80 | |
| 81 | ConversionData &m_cd; |
| 82 | }; |
| 83 | |
| 84 | void TSReader::handleError() |
| 85 | { |
| 86 | if (isComment()) |
| 87 | return; |
| 88 | if (hasError() && error() == CustomError) // raised by readContents |
| 89 | return; |
| 90 | |
| 91 | const QString loc = QString::fromLatin1(str: "at %3:%1:%2" ) |
| 92 | .arg(a: lineNumber()).arg(a: columnNumber()).arg(a: m_cd.m_sourceFileName); |
| 93 | |
| 94 | switch (tokenType()) { |
| 95 | case NoToken: // Cannot happen |
| 96 | default: // likewise |
| 97 | case Invalid: |
| 98 | raiseError(message: QString::fromLatin1(str: "Parse error %1: %2" ).arg(args: loc, args: errorString())); |
| 99 | break; |
| 100 | case StartElement: |
| 101 | raiseError(message: QString::fromLatin1(str: "Unexpected tag <%1> %2" ).arg(args: name().toString(), args: loc)); |
| 102 | break; |
| 103 | case Characters: |
| 104 | { |
| 105 | QString tok = text().toString(); |
| 106 | if (tok.length() > 30) |
| 107 | tok = tok.left(n: 30) + QLatin1String("[...]" ); |
| 108 | raiseError(message: QString::fromLatin1(str: "Unexpected characters '%1' %2" ).arg(args&: tok, args: loc)); |
| 109 | } |
| 110 | break; |
| 111 | case EntityReference: |
| 112 | raiseError(message: QString::fromLatin1(str: "Unexpected entity '&%1;' %2" ).arg(args: name().toString(), args: loc)); |
| 113 | break; |
| 114 | case ProcessingInstruction: |
| 115 | raiseError(message: QString::fromLatin1(str: "Unexpected processing instruction %1" ).arg(a: loc)); |
| 116 | break; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | static QString byteValue(QString value) |
| 121 | { |
| 122 | int base = 10; |
| 123 | if (value.startsWith(s: QLatin1String("x" ))) { |
| 124 | base = 16; |
| 125 | value.remove(i: 0, len: 1); |
| 126 | } |
| 127 | int n = value.toUInt(ok: 0, base); |
| 128 | return (n != 0) ? QString(QChar(n)) : QString(); |
| 129 | } |
| 130 | |
| 131 | QString TSReader::readContents() |
| 132 | { |
| 133 | STRING(byte); |
| 134 | STRING(value); |
| 135 | |
| 136 | QString result; |
| 137 | while (!atEnd()) { |
| 138 | readNext(); |
| 139 | if (isEndElement()) { |
| 140 | break; |
| 141 | } else if (isCharacters()) { |
| 142 | result += text(); |
| 143 | } else if (elementStarts(str: strbyte)) { |
| 144 | // <byte value="..."> |
| 145 | result += byteValue(value: attributes().value(qualifiedName: strvalue).toString()); |
| 146 | readNext(); |
| 147 | if (!isEndElement()) { |
| 148 | handleError(); |
| 149 | break; |
| 150 | } |
| 151 | } else { |
| 152 | handleError(); |
| 153 | break; |
| 154 | } |
| 155 | } |
| 156 | //qDebug() << "TEXT: " << result; |
| 157 | return result; |
| 158 | } |
| 159 | |
| 160 | QString TSReader::readTransContents() |
| 161 | { |
| 162 | STRING(lengthvariant); |
| 163 | STRING(variants); |
| 164 | STRING(yes); |
| 165 | |
| 166 | if (attributes().value(qualifiedName: strvariants) == stryes) { |
| 167 | QString result; |
| 168 | while (!atEnd()) { |
| 169 | readNext(); |
| 170 | if (isEndElement()) { |
| 171 | break; |
| 172 | } else if (isWhiteSpace()) { |
| 173 | // ignore these, just whitespace |
| 174 | } else if (elementStarts(str: strlengthvariant)) { |
| 175 | if (!result.isEmpty()) |
| 176 | result += QChar(Translator::BinaryVariantSeparator); |
| 177 | result += readContents(); |
| 178 | } else { |
| 179 | handleError(); |
| 180 | break; |
| 181 | } |
| 182 | } |
| 183 | return result; |
| 184 | } else { |
| 185 | return readContents(); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | bool TSReader::read(Translator &translator) |
| 190 | { |
| 191 | STRING(catalog); |
| 192 | STRING(comment); |
| 193 | STRING(context); |
| 194 | STRING(defaultcodec); |
| 195 | STRING(dependencies); |
| 196 | STRING(dependency); |
| 197 | STRING(extracomment); |
| 198 | STRING(filename); |
| 199 | STRING(id); |
| 200 | STRING(language); |
| 201 | STRING(line); |
| 202 | STRING(location); |
| 203 | STRING(message); |
| 204 | STRING(name); |
| 205 | STRING(numerus); |
| 206 | STRING(numerusform); |
| 207 | STRING(obsolete); |
| 208 | STRING(oldcomment); |
| 209 | STRING(oldsource); |
| 210 | STRING(source); |
| 211 | STRING(sourcelanguage); |
| 212 | STRING(translation); |
| 213 | STRING(translatorcomment); |
| 214 | STRING(TS); |
| 215 | STRING(type); |
| 216 | STRING(unfinished); |
| 217 | STRING(userdata); |
| 218 | STRING(vanished); |
| 219 | //STRING(version); |
| 220 | STRING(yes); |
| 221 | |
| 222 | static const QString (QLatin1String("extra-" )); |
| 223 | |
| 224 | while (!atEnd()) { |
| 225 | readNext(); |
| 226 | if (isStartDocument()) { |
| 227 | // <!DOCTYPE TS> |
| 228 | //qDebug() << attributes(); |
| 229 | } else if (isEndDocument()) { |
| 230 | // <!DOCTYPE TS> |
| 231 | //qDebug() << attributes(); |
| 232 | } else if (isDTD()) { |
| 233 | // <!DOCTYPE TS> |
| 234 | //qDebug() << tokenString(); |
| 235 | } else if (elementStarts(str: strTS)) { |
| 236 | // <TS> |
| 237 | //qDebug() << "TS " << attributes(); |
| 238 | QHash<QString, int> currentLine; |
| 239 | QString currentFile; |
| 240 | bool maybeRelative = false, maybeAbsolute = false; |
| 241 | |
| 242 | QXmlStreamAttributes atts = attributes(); |
| 243 | //QString version = atts.value(strversion).toString(); |
| 244 | translator.setLanguageCode(atts.value(qualifiedName: strlanguage).toString()); |
| 245 | translator.setSourceLanguageCode(atts.value(qualifiedName: strsourcelanguage).toString()); |
| 246 | while (!atEnd()) { |
| 247 | readNext(); |
| 248 | if (isEndElement()) { |
| 249 | // </TS> found, finish local loop |
| 250 | break; |
| 251 | } else if (isWhiteSpace()) { |
| 252 | // ignore these, just whitespace |
| 253 | } else if (elementStarts(str: strdefaultcodec)) { |
| 254 | // <defaultcodec> |
| 255 | readElementText(); |
| 256 | m_cd.appendError(error: QString::fromLatin1(str: "Warning: ignoring <defaultcodec> element" )); |
| 257 | // </defaultcodec> |
| 258 | } else if (isStartElement() |
| 259 | && name().toString().startsWith(s: strextrans)) { |
| 260 | // <extra-...> |
| 261 | QString tag = name().toString(); |
| 262 | translator.setExtra(ba: tag.mid(position: 6), var: readContents()); |
| 263 | // </extra-...> |
| 264 | } else if (elementStarts(str: strdependencies)) { |
| 265 | /* |
| 266 | * <dependencies> |
| 267 | * <dependency catalog="qtsystems_no"/> |
| 268 | * <dependency catalog="qtbase_no"/> |
| 269 | * </dependencies> |
| 270 | **/ |
| 271 | QStringList dependencies; |
| 272 | while (!atEnd()) { |
| 273 | readNext(); |
| 274 | if (isEndElement()) { |
| 275 | // </dependencies> found, finish local loop |
| 276 | break; |
| 277 | } else if (elementStarts(str: strdependency)) { |
| 278 | // <dependency> |
| 279 | QXmlStreamAttributes atts = attributes(); |
| 280 | dependencies.append(t: atts.value(qualifiedName: strcatalog).toString()); |
| 281 | while (!atEnd()) { |
| 282 | readNext(); |
| 283 | if (isEndElement()) { |
| 284 | // </dependency> found, finish local loop |
| 285 | break; |
| 286 | } |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | translator.setDependencies(dependencies); |
| 291 | } else if (elementStarts(str: strcontext)) { |
| 292 | // <context> |
| 293 | QString context; |
| 294 | while (!atEnd()) { |
| 295 | readNext(); |
| 296 | if (isEndElement()) { |
| 297 | // </context> found, finish local loop |
| 298 | break; |
| 299 | } else if (isWhiteSpace()) { |
| 300 | // ignore these, just whitespace |
| 301 | } else if (elementStarts(str: strname)) { |
| 302 | // <name> |
| 303 | context = readElementText(); |
| 304 | // </name> |
| 305 | } else if (elementStarts(str: strmessage)) { |
| 306 | // <message> |
| 307 | TranslatorMessage::References refs; |
| 308 | QString currentMsgFile = currentFile; |
| 309 | |
| 310 | TranslatorMessage msg; |
| 311 | msg.setId(attributes().value(qualifiedName: strid).toString()); |
| 312 | msg.setContext(context); |
| 313 | msg.setType(TranslatorMessage::Finished); |
| 314 | msg.setPlural(attributes().value(qualifiedName: strnumerus) == stryes); |
| 315 | while (!atEnd()) { |
| 316 | readNext(); |
| 317 | if (isEndElement()) { |
| 318 | // </message> found, finish local loop |
| 319 | msg.setReferences(refs); |
| 320 | translator.append(msg); |
| 321 | break; |
| 322 | } else if (isWhiteSpace()) { |
| 323 | // ignore these, just whitespace |
| 324 | } else if (elementStarts(str: strsource)) { |
| 325 | // <source>...</source> |
| 326 | msg.setSourceText(readContents()); |
| 327 | } else if (elementStarts(str: stroldsource)) { |
| 328 | // <oldsource>...</oldsource> |
| 329 | msg.setOldSourceText(readContents()); |
| 330 | } else if (elementStarts(str: stroldcomment)) { |
| 331 | // <oldcomment>...</oldcomment> |
| 332 | msg.setOldComment(readContents()); |
| 333 | } else if (elementStarts(str: strextracomment)) { |
| 334 | // <extracomment>...</extracomment> |
| 335 | msg.setExtraComment(readContents()); |
| 336 | } else if (elementStarts(str: strtranslatorcomment)) { |
| 337 | // <translatorcomment>...</translatorcomment> |
| 338 | msg.setTranslatorComment(readContents()); |
| 339 | } else if (elementStarts(str: strlocation)) { |
| 340 | // <location/> |
| 341 | maybeAbsolute = true; |
| 342 | QXmlStreamAttributes atts = attributes(); |
| 343 | QString fileName = atts.value(qualifiedName: strfilename).toString(); |
| 344 | if (fileName.isEmpty()) { |
| 345 | fileName = currentMsgFile; |
| 346 | maybeRelative = true; |
| 347 | } else { |
| 348 | if (refs.isEmpty()) |
| 349 | currentFile = fileName; |
| 350 | currentMsgFile = fileName; |
| 351 | } |
| 352 | const QString lin = atts.value(qualifiedName: strline).toString(); |
| 353 | if (lin.isEmpty()) { |
| 354 | refs.append(t: TranslatorMessage::Reference(fileName, -1)); |
| 355 | } else { |
| 356 | bool bOK; |
| 357 | int lineNo = lin.toInt(ok: &bOK); |
| 358 | if (bOK) { |
| 359 | if (lin.startsWith(c: QLatin1Char('+')) || lin.startsWith(c: QLatin1Char('-'))) { |
| 360 | lineNo = (currentLine[fileName] += lineNo); |
| 361 | maybeRelative = true; |
| 362 | } |
| 363 | refs.append(t: TranslatorMessage::Reference(fileName, lineNo)); |
| 364 | } |
| 365 | } |
| 366 | readContents(); |
| 367 | } else if (elementStarts(str: strcomment)) { |
| 368 | // <comment>...</comment> |
| 369 | msg.setComment(readContents()); |
| 370 | } else if (elementStarts(str: struserdata)) { |
| 371 | // <userdata>...</userdata> |
| 372 | msg.setUserData(readContents()); |
| 373 | } else if (elementStarts(str: strtranslation)) { |
| 374 | // <translation> |
| 375 | QXmlStreamAttributes atts = attributes(); |
| 376 | QStringRef type = atts.value(qualifiedName: strtype); |
| 377 | if (type == strunfinished) |
| 378 | msg.setType(TranslatorMessage::Unfinished); |
| 379 | else if (type == strvanished) |
| 380 | msg.setType(TranslatorMessage::Vanished); |
| 381 | else if (type == strobsolete) |
| 382 | msg.setType(TranslatorMessage::Obsolete); |
| 383 | if (msg.isPlural()) { |
| 384 | QStringList translations; |
| 385 | while (!atEnd()) { |
| 386 | readNext(); |
| 387 | if (isEndElement()) { |
| 388 | break; |
| 389 | } else if (isWhiteSpace()) { |
| 390 | // ignore these, just whitespace |
| 391 | } else if (elementStarts(str: strnumerusform)) { |
| 392 | translations.append(t: readTransContents()); |
| 393 | } else { |
| 394 | handleError(); |
| 395 | break; |
| 396 | } |
| 397 | } |
| 398 | msg.setTranslations(translations); |
| 399 | } else { |
| 400 | msg.setTranslation(readTransContents()); |
| 401 | } |
| 402 | // </translation> |
| 403 | } else if (isStartElement() |
| 404 | && name().toString().startsWith(s: strextrans)) { |
| 405 | // <extra-...> |
| 406 | QString tag = name().toString(); |
| 407 | msg.setExtra(ba: tag.mid(position: 6), var: readContents()); |
| 408 | // </extra-...> |
| 409 | } else { |
| 410 | handleError(); |
| 411 | } |
| 412 | } |
| 413 | // </message> |
| 414 | } else { |
| 415 | handleError(); |
| 416 | } |
| 417 | } |
| 418 | // </context> |
| 419 | } else { |
| 420 | handleError(); |
| 421 | } |
| 422 | translator.setLocationsType(maybeRelative ? Translator::RelativeLocations : |
| 423 | maybeAbsolute ? Translator::AbsoluteLocations : |
| 424 | Translator::NoLocations); |
| 425 | } // </TS> |
| 426 | } else { |
| 427 | handleError(); |
| 428 | } |
| 429 | } |
| 430 | if (hasError()) { |
| 431 | m_cd.appendError(error: errorString()); |
| 432 | return false; |
| 433 | } |
| 434 | return true; |
| 435 | } |
| 436 | |
| 437 | static QString numericEntity(int ch) |
| 438 | { |
| 439 | return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>" ) |
| 440 | : QLatin1String("&#x%1;" )) .arg(a: ch, fieldWidth: 0, base: 16); |
| 441 | } |
| 442 | |
| 443 | static QString protect(const QString &str) |
| 444 | { |
| 445 | QString result; |
| 446 | result.reserve(asize: str.length() * 12 / 10); |
| 447 | for (int i = 0; i != str.size(); ++i) { |
| 448 | const QChar ch = str[i]; |
| 449 | uint c = ch.unicode(); |
| 450 | switch (c) { |
| 451 | case '\"': |
| 452 | result += QLatin1String(""" ); |
| 453 | break; |
| 454 | case '&': |
| 455 | result += QLatin1String("&" ); |
| 456 | break; |
| 457 | case '>': |
| 458 | result += QLatin1String(">" ); |
| 459 | break; |
| 460 | case '<': |
| 461 | result += QLatin1String("<" ); |
| 462 | break; |
| 463 | case '\'': |
| 464 | result += QLatin1String("'" ); |
| 465 | break; |
| 466 | default: |
| 467 | if ((c < 0x20 || (ch > QChar(0x7f) && ch.isSpace())) && c != '\n' && c != '\t') |
| 468 | result += numericEntity(ch: c); |
| 469 | else // this also covers surrogates |
| 470 | result += QChar(c); |
| 471 | } |
| 472 | } |
| 473 | return result; |
| 474 | } |
| 475 | |
| 476 | static void (QTextStream &t, const char *indent, |
| 477 | const TranslatorMessage::ExtraData &, QRegExp drops) |
| 478 | { |
| 479 | QStringList outs; |
| 480 | for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) { |
| 481 | if (!drops.exactMatch(str: it.key())) { |
| 482 | outs << (QStringLiteral("<extra-" ) + it.key() + QLatin1Char('>') |
| 483 | + protect(str: it.value()) |
| 484 | + QStringLiteral("</extra-" ) + it.key() + QLatin1Char('>')); |
| 485 | } |
| 486 | } |
| 487 | outs.sort(); |
| 488 | foreach (const QString &out, outs) |
| 489 | t << indent << out << Qt::endl; |
| 490 | } |
| 491 | |
| 492 | static void writeVariants(QTextStream &t, const char *indent, const QString &input) |
| 493 | { |
| 494 | int offset; |
| 495 | if ((offset = input.indexOf(c: QChar(Translator::BinaryVariantSeparator))) >= 0) { |
| 496 | t << " variants=\"yes\">" ; |
| 497 | int start = 0; |
| 498 | forever { |
| 499 | t << "\n " << indent << "<lengthvariant>" |
| 500 | << protect(str: input.mid(position: start, n: offset - start)) |
| 501 | << "</lengthvariant>" ; |
| 502 | if (offset == input.length()) |
| 503 | break; |
| 504 | start = offset + 1; |
| 505 | offset = input.indexOf(c: QChar(Translator::BinaryVariantSeparator), from: start); |
| 506 | if (offset < 0) |
| 507 | offset = input.length(); |
| 508 | } |
| 509 | t << "\n" << indent; |
| 510 | } else { |
| 511 | t << ">" << protect(str: input); |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd) |
| 516 | { |
| 517 | bool result = true; |
| 518 | QTextStream t(&dev); |
| 519 | t.setCodec(QTextCodec::codecForName(name: "UTF-8" )); |
| 520 | //qDebug() << translator.codecName(); |
| 521 | |
| 522 | // The xml prolog allows processors to easily detect the correct encoding |
| 523 | t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n" ; |
| 524 | |
| 525 | t << "<TS version=\"2.1\"" ; |
| 526 | |
| 527 | QString languageCode = translator.languageCode(); |
| 528 | if (!languageCode.isEmpty() && languageCode != QLatin1String("C" )) |
| 529 | t << " language=\"" << languageCode << "\"" ; |
| 530 | languageCode = translator.sourceLanguageCode(); |
| 531 | if (!languageCode.isEmpty() && languageCode != QLatin1String("C" )) |
| 532 | t << " sourcelanguage=\"" << languageCode << "\"" ; |
| 533 | t << ">\n" ; |
| 534 | |
| 535 | QStringList deps = translator.dependencies(); |
| 536 | if (!deps.isEmpty()) { |
| 537 | t << "<dependencies>\n" ; |
| 538 | foreach (const QString &dep, deps) |
| 539 | t << "<dependency catalog=\"" << dep << "\"/>\n" ; |
| 540 | t << "</dependencies>\n" ; |
| 541 | } |
| 542 | |
| 543 | QRegExp drops(cd.dropTags().join(sep: QLatin1Char('|'))); |
| 544 | |
| 545 | writeExtras(t, indent: " " , extras: translator.extras(), drops); |
| 546 | |
| 547 | QHash<QString, QList<TranslatorMessage> > messageOrder; |
| 548 | QList<QString> contextOrder; |
| 549 | foreach (const TranslatorMessage &msg, translator.messages()) { |
| 550 | // no need for such noise |
| 551 | if ((msg.type() == TranslatorMessage::Obsolete || msg.type() == TranslatorMessage::Vanished) |
| 552 | && msg.translation().isEmpty()) { |
| 553 | continue; |
| 554 | } |
| 555 | |
| 556 | QList<TranslatorMessage> &context = messageOrder[msg.context()]; |
| 557 | if (context.isEmpty()) |
| 558 | contextOrder.append(t: msg.context()); |
| 559 | context.append(t: msg); |
| 560 | } |
| 561 | if (cd.sortContexts()) |
| 562 | std::sort(first: contextOrder.begin(), last: contextOrder.end()); |
| 563 | |
| 564 | QHash<QString, int> currentLine; |
| 565 | QString currentFile; |
| 566 | foreach (const QString &context, contextOrder) { |
| 567 | t << "<context>\n" |
| 568 | " <name>" |
| 569 | << protect(str: context) |
| 570 | << "</name>\n" ; |
| 571 | foreach (const TranslatorMessage &msg, messageOrder[context]) { |
| 572 | //msg.dump(); |
| 573 | |
| 574 | t << " <message" ; |
| 575 | if (!msg.id().isEmpty()) |
| 576 | t << " id=\"" << msg.id() << "\"" ; |
| 577 | if (msg.isPlural()) |
| 578 | t << " numerus=\"yes\"" ; |
| 579 | t << ">\n" ; |
| 580 | if (translator.locationsType() != Translator::NoLocations) { |
| 581 | QString cfile = currentFile; |
| 582 | bool first = true; |
| 583 | foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) { |
| 584 | QString fn = cd.m_targetDir.relativeFilePath(fileName: ref.fileName()) |
| 585 | .replace(before: QLatin1Char('\\'),after: QLatin1Char('/')); |
| 586 | int ln = ref.lineNumber(); |
| 587 | QString ld; |
| 588 | if (translator.locationsType() == Translator::RelativeLocations) { |
| 589 | if (ln != -1) { |
| 590 | int dlt = ln - currentLine[fn]; |
| 591 | if (dlt >= 0) |
| 592 | ld.append(c: QLatin1Char('+')); |
| 593 | ld.append(s: QString::number(dlt)); |
| 594 | currentLine[fn] = ln; |
| 595 | } |
| 596 | |
| 597 | if (fn != cfile) { |
| 598 | if (first) |
| 599 | currentFile = fn; |
| 600 | cfile = fn; |
| 601 | } else { |
| 602 | fn.clear(); |
| 603 | } |
| 604 | first = false; |
| 605 | } else { |
| 606 | if (ln != -1) |
| 607 | ld = QString::number(ln); |
| 608 | } |
| 609 | t << " <location" ; |
| 610 | if (!fn.isEmpty()) |
| 611 | t << " filename=\"" << fn << "\"" ; |
| 612 | if (!ld.isEmpty()) |
| 613 | t << " line=\"" << ld << "\"" ; |
| 614 | t << "/>\n" ; |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | t << " <source>" |
| 619 | << protect(str: msg.sourceText()) |
| 620 | << "</source>\n" ; |
| 621 | |
| 622 | if (!msg.oldSourceText().isEmpty()) |
| 623 | t << " <oldsource>" << protect(str: msg.oldSourceText()) << "</oldsource>\n" ; |
| 624 | |
| 625 | if (!msg.comment().isEmpty()) { |
| 626 | t << " <comment>" |
| 627 | << protect(str: msg.comment()) |
| 628 | << "</comment>\n" ; |
| 629 | } |
| 630 | |
| 631 | if (!msg.oldComment().isEmpty()) |
| 632 | t << " <oldcomment>" << protect(str: msg.oldComment()) << "</oldcomment>\n" ; |
| 633 | |
| 634 | if (!msg.extraComment().isEmpty()) |
| 635 | t << " <extracomment>" << protect(str: msg.extraComment()) |
| 636 | << "</extracomment>\n" ; |
| 637 | |
| 638 | if (!msg.translatorComment().isEmpty()) |
| 639 | t << " <translatorcomment>" << protect(str: msg.translatorComment()) |
| 640 | << "</translatorcomment>\n" ; |
| 641 | |
| 642 | t << " <translation" ; |
| 643 | if (msg.type() == TranslatorMessage::Unfinished) |
| 644 | t << " type=\"unfinished\"" ; |
| 645 | else if (msg.type() == TranslatorMessage::Vanished) |
| 646 | t << " type=\"vanished\"" ; |
| 647 | else if (msg.type() == TranslatorMessage::Obsolete) |
| 648 | t << " type=\"obsolete\"" ; |
| 649 | if (msg.isPlural()) { |
| 650 | t << ">" ; |
| 651 | const QStringList &translns = msg.translations(); |
| 652 | for (int j = 0; j < translns.count(); ++j) { |
| 653 | t << "\n <numerusform" ; |
| 654 | writeVariants(t, indent: " " , input: translns[j]); |
| 655 | t << "</numerusform>" ; |
| 656 | } |
| 657 | t << "\n " ; |
| 658 | } else { |
| 659 | writeVariants(t, indent: " " , input: msg.translation()); |
| 660 | } |
| 661 | t << "</translation>\n" ; |
| 662 | |
| 663 | writeExtras(t, indent: " " , extras: msg.extras(), drops); |
| 664 | |
| 665 | if (!msg.userData().isEmpty()) |
| 666 | t << " <userdata>" << msg.userData() << "</userdata>\n" ; |
| 667 | t << " </message>\n" ; |
| 668 | } |
| 669 | t << "</context>\n" ; |
| 670 | } |
| 671 | |
| 672 | t << "</TS>\n" ; |
| 673 | return result; |
| 674 | } |
| 675 | |
| 676 | bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd) |
| 677 | { |
| 678 | TSReader reader(dev, cd); |
| 679 | return reader.read(translator); |
| 680 | } |
| 681 | |
| 682 | int initTS() |
| 683 | { |
| 684 | Translator::FileFormat format; |
| 685 | |
| 686 | format.extension = QLatin1String("ts" ); |
| 687 | format.fileType = Translator::FileFormat::TranslationSource; |
| 688 | format.priority = 0; |
| 689 | format.untranslatedDescription = QT_TRANSLATE_NOOP("FMT" , "Qt translation sources" ); |
| 690 | format.loader = &loadTS; |
| 691 | format.saver = &saveTS; |
| 692 | Translator::registerFileFormat(format); |
| 693 | |
| 694 | return 1; |
| 695 | } |
| 696 | |
| 697 | Q_CONSTRUCTOR_FUNCTION(initTS) |
| 698 | |
| 699 | QT_END_NAMESPACE |
| 700 | |