1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "lupdate.h"
5
6#include <translator.h>
7#include <xmlparser.h>
8
9#include <QtCore/QCoreApplication>
10#include <QtCore/QDebug>
11#include <QtCore/QFile>
12#include <QtCore/QString>
13#include <QtCore/QXmlStreamReader>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19class UiReader : public XmlParser
20{
21public:
22 UiReader(Translator &translator, ConversionData &cd, QXmlStreamReader &reader)
23 : XmlParser(reader),
24 m_translator(translator),
25 m_cd(cd),
26 m_lineNumber(-1),
27 m_isTrString(false),
28 m_insideStringList(false),
29 m_idBasedTranslations(false)
30 {
31 }
32 ~UiReader() override = default;
33
34private:
35 bool startElement(QStringView namespaceURI, QStringView localName,
36 QStringView qName, const QXmlStreamAttributes &atts) override;
37 bool endElement(QStringView namespaceURI, QStringView localName,
38 QStringView qName) override;
39 bool characters(QStringView ch) override;
40 bool fatalError(qint64 line, qint64 column, const QString &message) override;
41
42 void flush();
43 void readTranslationAttributes(const QXmlStreamAttributes &atts);
44
45 Translator &m_translator;
46 ConversionData &m_cd;
47 QString m_context;
48 QString m_source;
49 QString m_comment;
50 QString m_extracomment;
51 QString m_id;
52
53 QString m_accum;
54 int m_lineNumber;
55 bool m_isTrString;
56 bool m_insideStringList;
57 bool m_idBasedTranslations;
58};
59
60bool UiReader::startElement(QStringView namespaceURI, QStringView localName,
61 QStringView qName, const QXmlStreamAttributes &atts)
62{
63 Q_UNUSED(namespaceURI);
64 Q_UNUSED(localName);
65
66 if (qName == QLatin1String("string")) {
67 flush();
68 if (!m_insideStringList)
69 readTranslationAttributes(atts);
70 } else if (qName == QLatin1String("stringlist")) {
71 flush();
72 m_insideStringList = true;
73 readTranslationAttributes(atts);
74 } else if (qName == QLatin1String("ui")) { // UI "header"
75 const auto attr = QStringLiteral("idbasedtr");
76 m_idBasedTranslations =
77 atts.hasAttribute(qualifiedName: attr) && atts.value(qualifiedName: attr) == QLatin1String("true");
78 }
79 m_accum.clear();
80 return true;
81}
82
83bool UiReader::endElement(QStringView namespaceURI, QStringView localName,
84 QStringView qName)
85{
86 Q_UNUSED(namespaceURI);
87 Q_UNUSED(localName);
88
89 m_accum.replace(before: QLatin1String("\r\n"), after: QLatin1String("\n"));
90
91 if (qName == QLatin1String("class")) { // UI "header"
92 if (m_context.isEmpty())
93 m_context = m_accum;
94 } else if (qName == QLatin1String("string") && m_isTrString) {
95 m_source = m_accum;
96 } else if (qName == QLatin1String("comment")) { // FIXME: what's that?
97 m_comment = m_accum;
98 flush();
99 } else if (qName == QLatin1String("stringlist")) {
100 m_insideStringList = false;
101 } else {
102 flush();
103 }
104 return true;
105}
106
107bool UiReader::characters(QStringView ch)
108{
109 m_accum += ch.toString();
110 return true;
111}
112
113bool UiReader::fatalError(qint64 line, qint64 column, const QString &message)
114{
115 QString msg = QStringLiteral("XML error: Parse error at line %1, column %2 (%3).")
116 .arg(a: line)
117 .arg(a: column)
118 .arg(a: message);
119 m_cd.appendError(error: msg);
120 return false;
121}
122
123void UiReader::flush()
124{
125 if (!m_context.isEmpty() && !m_source.isEmpty()) {
126 TranslatorMessage msg(m_context, m_source,
127 m_comment, QString(), m_cd.m_sourceFileName,
128 m_lineNumber, QStringList());
129 msg.setExtraComment(m_extracomment);
130 msg.setId(m_id);
131 m_translator.extend(msg, cd&: m_cd);
132 }
133 m_source.clear();
134 if (!m_insideStringList) {
135 m_comment.clear();
136 m_extracomment.clear();
137 m_id.clear();
138 }
139}
140
141void UiReader::readTranslationAttributes(const QXmlStreamAttributes &atts)
142{
143 const auto notr = atts.value(QStringLiteral("notr"));
144 if (notr.isEmpty() || notr != QStringLiteral("true")) {
145 m_isTrString = true;
146 m_comment = atts.value(QStringLiteral("comment")).toString();
147 m_extracomment = atts.value(QStringLiteral("extracomment")).toString();
148 if (m_idBasedTranslations)
149 m_id = atts.value(QStringLiteral("id")).toString();
150 if (!m_cd.m_noUiLines)
151 m_lineNumber = static_cast<int>(reader.lineNumber());
152 } else {
153 m_isTrString = false;
154 }
155}
156
157bool loadUI(Translator &translator, const QString &filename, ConversionData &cd)
158{
159 cd.m_sourceFileName = filename;
160 QFile file(filename);
161 if (!file.open(flags: QIODevice::ReadOnly)) {
162 cd.appendError(QStringLiteral("Cannot open %1: %2").arg(args: filename, args: file.errorString()));
163 return false;
164 }
165
166 QXmlStreamReader reader(&file);
167 reader.setNamespaceProcessing(false);
168
169 UiReader uiReader(translator, cd, reader);
170 bool result = uiReader.parse();
171 if (!result)
172 cd.appendError(error: u"Parse error in UI file"_s);
173 return result;
174}
175
176QT_END_NAMESPACE
177

source code of qttools/src/linguist/lupdate/ui.cpp