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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | static QString xmlProtect(const QString & str) |
18 | { |
19 | QString p = str; |
20 | p.replace(c: QLatin1Char('&'), after: QLatin1String("&")); |
21 | p.replace(c: QLatin1Char('\"'), after: QLatin1String(""")); |
22 | p.replace(c: QLatin1Char('>'), after: QLatin1String(">")); |
23 | p.replace(c: QLatin1Char('<'), after: QLatin1String("<")); |
24 | p.replace(c: QLatin1Char('\''), after: QLatin1String("'")); |
25 | return p; |
26 | } |
27 | |
28 | Phrase::Phrase() |
29 | : shrtc(-1), m_phraseBook(0) |
30 | { |
31 | } |
32 | |
33 | Phrase::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 | |
39 | Phrase::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 | |
46 | void 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 | |
55 | void 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 | |
64 | void 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 | |
73 | bool 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 | |
79 | class QphHandler : public XmlParser |
80 | { |
81 | public: |
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 | |
91 | private: |
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 | |
110 | bool 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 | |
128 | bool 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 | |
145 | bool QphHandler::characters(QStringView ch) |
146 | { |
147 | accum += ch; |
148 | return true; |
149 | } |
150 | |
151 | bool 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 | |
163 | PhraseBook::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 | |
172 | PhraseBook::~PhraseBook() |
173 | { |
174 | qDeleteAll(c: m_phrases); |
175 | } |
176 | |
177 | void 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 | |
186 | void 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 | |
195 | bool 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 | |
237 | bool 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 | |
269 | void PhraseBook::append(Phrase *phrase) |
270 | { |
271 | m_phrases.append(t: phrase); |
272 | phrase->setPhraseBook(this); |
273 | setModified(true); |
274 | emit listChanged(); |
275 | } |
276 | |
277 | void PhraseBook::remove(Phrase *phrase) |
278 | { |
279 | m_phrases.removeOne(t: phrase); |
280 | phrase->setPhraseBook(0); |
281 | setModified(true); |
282 | emit listChanged(); |
283 | } |
284 | |
285 | void PhraseBook::setModified(bool modified) |
286 | { |
287 | if (m_changed != modified) { |
288 | emit modifiedChanged(changed: modified); |
289 | m_changed = modified; |
290 | } |
291 | } |
292 | |
293 | void PhraseBook::phraseChanged(Phrase *p) |
294 | { |
295 | Q_UNUSED(p); |
296 | |
297 | setModified(true); |
298 | } |
299 | |
300 | QString PhraseBook::friendlyPhraseBookName() const |
301 | { |
302 | if (!m_fileName.isEmpty()) |
303 | return QFileInfo(m_fileName).fileName(); |
304 | return QString(); |
305 | } |
306 | |
307 | QT_END_NAMESPACE |
308 |
Definitions
Learn Advanced QML with KDAB
Find out more