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 {
30 }
31 ~UiReader() override = default;
32
33private:
34 bool startElement(QStringView namespaceURI, QStringView localName,
35 QStringView qName, const QXmlStreamAttributes &atts) override;
36 bool endElement(QStringView namespaceURI, QStringView localName,
37 QStringView qName) override;
38 bool characters(QStringView ch) override;
39 bool fatalError(qint64 line, qint64 column, const QString &message) override;
40
41 void flush();
42 void readTranslationAttributes(const QXmlStreamAttributes &atts);
43
44 Translator &m_translator;
45 ConversionData &m_cd;
46 QString m_context;
47 QString m_source;
48 QString m_comment;
49 QString m_extracomment;
50 QString m_id;
51 QString m_label;
52
53 QString m_accum;
54 int m_lineNumber;
55 bool m_isTrString;
56 bool m_insideStringList;
57};
58
59bool UiReader::startElement(QStringView namespaceURI, QStringView localName,
60 QStringView qName, const QXmlStreamAttributes &atts)
61{
62 Q_UNUSED(namespaceURI);
63 Q_UNUSED(localName);
64
65 if (qName == "string"_L1) {
66 flush();
67 if (!m_insideStringList)
68 readTranslationAttributes(atts);
69 } else if (qName == "stringlist"_L1) {
70 flush();
71 m_insideStringList = true;
72 readTranslationAttributes(atts);
73 }
74 m_accum.clear();
75 return true;
76}
77
78bool UiReader::endElement(QStringView namespaceURI, QStringView localName,
79 QStringView qName)
80{
81 Q_UNUSED(namespaceURI);
82 Q_UNUSED(localName);
83
84 m_accum.replace(before: "\r\n"_L1, after: "\n"_L1);
85
86 if (qName == "class"_L1) { // UI "header"
87 if (m_context.isEmpty())
88 m_context = m_accum;
89 } else if (qName == "string"_L1 && m_isTrString) {
90 m_source = m_accum;
91 } else if (qName == "comment"_L1) { // FIXME: what's that?
92 m_comment = m_accum;
93 flush();
94 } else if (qName == "stringlist"_L1) {
95 m_insideStringList = false;
96 } else {
97 flush();
98 }
99 return true;
100}
101
102bool UiReader::characters(QStringView ch)
103{
104 m_accum += ch.toString();
105 return true;
106}
107
108bool UiReader::fatalError(qint64 line, qint64 column, const QString &message)
109{
110 QString msg = QStringLiteral("XML error: Parse error at line %1, column %2 (%3).")
111 .arg(a: line)
112 .arg(a: column)
113 .arg(a: message);
114 m_cd.appendError(error: msg);
115 return false;
116}
117
118void UiReader::flush()
119{
120 if ((!m_context.isEmpty() || !m_id.isEmpty()) && !m_source.isEmpty()) {
121 TranslatorMessage msg(m_context, m_source,
122 m_comment, QString(), m_cd.m_sourceFileName,
123 m_lineNumber, QStringList());
124 msg.setExtraComment(m_extracomment);
125 msg.setLabel(m_label);
126 msg.setId(m_id);
127 if (!m_id.isEmpty())
128 msg.setContext({});
129 m_translator.extend(msg, cd&: m_cd);
130 }
131 m_source.clear();
132 if (!m_insideStringList) {
133 m_comment.clear();
134 m_extracomment.clear();
135 m_id.clear();
136 }
137}
138
139void UiReader::readTranslationAttributes(const QXmlStreamAttributes &atts)
140{
141 const auto notr = atts.value(QStringLiteral("notr"));
142 if (notr.isEmpty() || notr != QStringLiteral("true")) {
143 m_isTrString = true;
144 m_comment = atts.value(QStringLiteral("comment")).toString();
145 m_extracomment = atts.value(QStringLiteral("extracomment")).toString();
146 m_id = atts.value(QStringLiteral("id")).toString();
147 QString label = atts.value(QStringLiteral("label")).toString();
148 if (!m_id.isEmpty())
149 m_label = std::move(label);
150 else if (!label.isEmpty())
151 m_cd.appendError(error: "%1:%2: labels cannot be used with text-based translation. "
152 "Ignoring\n"_L1.arg(args&: m_cd.m_sourceFileName)
153 .arg(a: m_lineNumber));
154 if (!m_cd.m_noUiLines)
155 m_lineNumber = static_cast<int>(reader.lineNumber());
156 } else {
157 m_isTrString = false;
158 }
159}
160
161bool loadUI(Translator &translator, const QString &filename, ConversionData &cd)
162{
163 cd.m_sourceFileName = filename;
164 QFile file(filename);
165 if (!file.open(flags: QIODevice::ReadOnly)) {
166 cd.appendError(error: "Cannot open %1: %2"_L1.arg(args: filename, args: file.errorString()));
167 return false;
168 }
169
170 QXmlStreamReader reader(&file);
171 reader.setNamespaceProcessing(false);
172
173 UiReader uiReader(translator, cd, reader);
174 bool result = uiReader.parse();
175 if (!result)
176 cd.appendError(error: u"Parse error in UI file"_s);
177 return result;
178}
179
180QT_END_NAMESPACE
181

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