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

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