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 "phrase.h"
5#include "translator.h"
6#include "xmlparser.h"
7
8#include <QApplication>
9#include <QFile>
10#include <QFileInfo>
11#include <QMessageBox>
12#include <QTextStream>
13#include <QXmlStreamReader>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::Literals::StringLiterals;
18
19static QString xmlProtect(const QString & str)
20{
21 QString p = str;
22 p.replace(c: u'&', after: "&amp;"_L1);
23 p.replace(c: u'\"', after: "&quot;"_L1);
24 p.replace(c: u'>', after: "&gt;"_L1);
25 p.replace(c: u'<', after: "&lt;"_L1);
26 p.replace(c: QLatin1Char('\''), after: "&apos;"_L1);
27 return p;
28}
29
30Phrase::Phrase()
31 : shrtc(-1), m_phraseBook(0)
32{
33}
34
35Phrase::Phrase(const QString &source, const QString &target, const QString &definition,
36 const Candidate &candidate, int sc)
37 : shrtc(sc), s(source), t(target), d(definition), cand(candidate), m_phraseBook(0)
38{
39}
40
41Phrase::Phrase(const QString &source, const QString &target,
42 const QString &definition, PhraseBook *phraseBook)
43 : shrtc(-1), s(source), t(target), d(definition),
44 m_phraseBook(phraseBook)
45{
46}
47
48void Phrase::setSource(const QString &ns)
49{
50 if (s == ns)
51 return;
52 s = ns;
53 if (m_phraseBook)
54 m_phraseBook->phraseChanged(phrase: this);
55}
56
57void Phrase::setTarget(const QString &nt)
58{
59 if (t == nt)
60 return;
61 t = nt;
62 if (m_phraseBook)
63 m_phraseBook->phraseChanged(phrase: this);
64}
65
66void Phrase::setDefinition(const QString &nd)
67{
68 if (d == nd)
69 return;
70 d = nd;
71 if (m_phraseBook)
72 m_phraseBook->phraseChanged(phrase: this);
73}
74
75bool operator==(const Phrase &p, const Phrase &q)
76{
77 return p.source() == q.source() && p.target() == q.target() &&
78 p.definition() == q.definition() && p.phraseBook() == q.phraseBook();
79}
80
81class QphHandler : public XmlParser
82{
83public:
84 QphHandler(PhraseBook *phraseBook, QXmlStreamReader &reader)
85 : XmlParser(reader), pb(phraseBook), ferrorCount(0)
86 {
87 }
88 ~QphHandler() override = default;
89
90 QString language() const { return m_language; }
91 QString sourceLanguage() const { return m_sourceLanguage; }
92
93private:
94 bool startElement(QStringView namespaceURI, QStringView localName,
95 QStringView qName, const QXmlStreamAttributes &atts) override;
96 bool endElement(QStringView namespaceURI, QStringView localName,
97 QStringView qName) override;
98 bool characters(QStringView ch) override;
99 bool fatalError(qint64 line, qint64 column, const QString &message) override;
100
101 PhraseBook *pb;
102 QString source;
103 QString target;
104 QString definition;
105 QString m_language;
106 QString m_sourceLanguage;
107
108 QString accum;
109 int ferrorCount;
110};
111
112bool QphHandler::startElement(QStringView namespaceURI, QStringView localName,
113 QStringView qName, const QXmlStreamAttributes &atts)
114{
115 Q_UNUSED(namespaceURI);
116 Q_UNUSED(localName);
117
118 if (qName == "QPH"_L1) {
119 m_language = atts.value(qualifiedName: "language"_L1).toString();
120 m_sourceLanguage = atts.value(qualifiedName: "sourcelanguage"_L1).toString();
121 } else if (qName == "phrase"_L1) {
122 source.truncate(pos: 0);
123 target.truncate(pos: 0);
124 definition.truncate(pos: 0);
125 }
126 accum.truncate(pos: 0);
127 return true;
128}
129
130bool QphHandler::endElement(QStringView namespaceURI, QStringView localName,
131 QStringView qName)
132{
133 Q_UNUSED(namespaceURI);
134 Q_UNUSED(localName);
135
136 if (qName == "source"_L1)
137 source = accum;
138 else if (qName == "target"_L1)
139 target = accum;
140 else if (qName == "definition"_L1)
141 definition = accum;
142 else if (qName == "phrase"_L1)
143 pb->m_phrases.append(t: new Phrase(source, target, definition, pb));
144 return true;
145}
146
147bool QphHandler::characters(QStringView ch)
148{
149 accum += ch;
150 return true;
151}
152
153bool QphHandler::fatalError(qint64 line, qint64 column, const QString &message)
154{
155 if (ferrorCount++ == 0) {
156 QString msg = PhraseBook::tr(s: "Parse error at line %1, column %2 (%3).")
157 .arg(a: line)
158 .arg(a: column)
159 .arg(a: message);
160 QMessageBox::information(parent: nullptr, title: QObject::tr(s: "Qt Linguist"), text: msg);
161 }
162 return false;
163}
164
165PhraseBook::PhraseBook() :
166 m_changed(false),
167 m_language(QLocale::C),
168 m_sourceLanguage(QLocale::C),
169 m_territory(QLocale::AnyTerritory),
170 m_sourceTerritory(QLocale::AnyTerritory)
171{
172}
173
174PhraseBook::~PhraseBook()
175{
176 qDeleteAll(c: m_phrases);
177}
178
179void PhraseBook::setLanguageAndTerritory(QLocale::Language lang, QLocale::Territory territory)
180{
181 if (m_language == lang && m_territory == territory)
182 return;
183 m_language = lang;
184 m_territory = territory;
185 setModified(true);
186}
187
188void PhraseBook::setSourceLanguageAndTerritory(QLocale::Language lang, QLocale::Territory territory)
189{
190 if (m_sourceLanguage == lang && m_sourceTerritory == territory)
191 return;
192 m_sourceLanguage = lang;
193 m_sourceTerritory = territory;
194 setModified(true);
195}
196
197bool PhraseBook::load(const QString &fileName, bool *langGuessed)
198{
199 QFile f(fileName);
200 if (!f.open(flags: QIODevice::ReadOnly))
201 return false;
202
203 m_fileName = fileName;
204
205 QXmlStreamReader reader(&f);
206 QphHandler *hand = new QphHandler(this, reader);
207 reader.setNamespaceProcessing(false);
208 bool ok = hand->parse();
209
210 Translator::languageAndTerritory(languageCode: hand->language(), langPtr: &m_language, territoryPtr: &m_territory);
211 *langGuessed = false;
212 if (m_language == QLocale::C) {
213 QLocale sys;
214 m_language = sys.language();
215 m_territory = sys.territory();
216 *langGuessed = true;
217 }
218
219 QString lang = hand->sourceLanguage();
220 if (lang.isEmpty()) {
221 m_sourceLanguage = QLocale::C;
222 m_sourceTerritory = QLocale::AnyTerritory;
223 } else {
224 Translator::languageAndTerritory(languageCode: lang, langPtr: &m_sourceLanguage, territoryPtr: &m_sourceTerritory);
225 }
226
227 delete hand;
228 f.close();
229 if (!ok) {
230 qDeleteAll(c: m_phrases);
231 m_phrases.clear();
232 } else {
233 emit listChanged();
234 }
235
236 return ok;
237}
238
239bool PhraseBook::save(const QString &fileName)
240{
241 QFile f(fileName);
242 if (!f.open(flags: QIODevice::WriteOnly))
243 return false;
244
245 m_fileName = fileName;
246
247 QTextStream t(&f);
248
249 t << "<!DOCTYPE QPH>\n<QPH";
250 if (sourceLanguage() != QLocale::C)
251 t << " sourcelanguage=\""
252 << Translator::makeLanguageCode(language: sourceLanguage(), territory: sourceTerritory()) << '"';
253 if (language() != QLocale::C)
254 t << " language=\"" << Translator::makeLanguageCode(language: language(), territory: territory()) << '"';
255 t << ">\n";
256 for (Phrase *p : std::as_const(t&: m_phrases)) {
257 t << "<phrase>\n";
258 t << " <source>" << xmlProtect( str: p->source() ) << "</source>\n";
259 t << " <target>" << xmlProtect( str: p->target() ) << "</target>\n";
260 if (!p->definition().isEmpty())
261 t << " <definition>" << xmlProtect( str: p->definition() )
262 << "</definition>\n";
263 t << "</phrase>\n";
264 }
265 t << "</QPH>\n";
266 f.close();
267 setModified(false);
268 return true;
269}
270
271void PhraseBook::append(Phrase *phrase)
272{
273 m_phrases.append(t: phrase);
274 phrase->setPhraseBook(this);
275 setModified(true);
276 emit listChanged();
277}
278
279void PhraseBook::remove(Phrase *phrase)
280{
281 m_phrases.removeOne(t: phrase);
282 phrase->setPhraseBook(0);
283 setModified(true);
284 emit listChanged();
285}
286
287void PhraseBook::setModified(bool modified)
288 {
289 if (m_changed != modified) {
290 emit modifiedChanged(changed: modified);
291 m_changed = modified;
292 }
293}
294
295void PhraseBook::phraseChanged(Phrase *p)
296{
297 Q_UNUSED(p);
298
299 setModified(true);
300}
301
302QString PhraseBook::friendlyPhraseBookName() const
303{
304 if (!m_fileName.isEmpty())
305 return QFileInfo(m_fileName).fileName();
306 return QString();
307}
308
309QT_END_NAMESPACE
310

source code of qttools/src/linguist/linguist/phrase.cpp