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