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 
protect(const QString & str)44 static QString protect(const QString & str)
45 {
46     QString p = str;
47     p.replace(QLatin1Char('&'),  QLatin1String("&amp;"));
48     p.replace(QLatin1Char('\"'), QLatin1String("&quot;"));
49     p.replace(QLatin1Char('>'),  QLatin1String("&gt;"));
50     p.replace(QLatin1Char('<'),  QLatin1String("&lt;"));
51     p.replace(QLatin1Char('\''), QLatin1String("&apos;"));
52     return p;
53 }
54 
Phrase()55 Phrase::Phrase()
56     : shrtc(-1), m_phraseBook(0)
57 {
58 }
59 
Phrase(const QString & source,const QString & target,const QString & definition,const Candidate & candidate,int sc)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 
Phrase(const QString & source,const QString & target,const QString & definition,PhraseBook * phraseBook)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 
setSource(const QString & ns)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(this);
80 }
81 
setTarget(const QString & nt)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(this);
89 }
90 
setDefinition(const QString & nd)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(this);
98 }
99 
operator ==(const Phrase & p,const Phrase & q)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:
QphHandler(PhraseBook * phraseBook,QXmlStreamReader & reader)109     QphHandler(PhraseBook *phraseBook, QXmlStreamReader &reader)
110         : XmlParser(reader), pb(phraseBook), ferrorCount(0)
111     {
112     }
113     ~QphHandler() override = default;
114 
language() const115     QString language() const { return m_language; }
sourceLanguage() const116     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 
startElement(const QStringRef & namespaceURI,const QStringRef & localName,const QStringRef & qName,const QXmlStreamAttributes & atts)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(QLatin1String("language")).toString();
145         m_sourceLanguage = atts.value(QLatin1String("sourcelanguage")).toString();
146     } else if (qName == QLatin1String("phrase")) {
147         source.truncate(0);
148         target.truncate(0);
149         definition.truncate(0);
150     }
151     accum.truncate(0);
152     return true;
153 }
154 
endElement(const QStringRef & namespaceURI,const QStringRef & localName,const QStringRef & qName)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(new Phrase(source, target, definition, pb));
169     return true;
170 }
171 
characters(const QStringRef & ch)172 bool QphHandler::characters(const QStringRef &ch)
173 {
174     accum += ch;
175     return true;
176 }
177 
fatalError(qint64 line,qint64 column,const QString & message)178 bool QphHandler::fatalError(qint64 line, qint64 column, const QString &message)
179 {
180     if (ferrorCount++ == 0) {
181         QString msg = PhraseBook::tr("Parse error at line %1, column %2 (%3).")
182                               .arg(line)
183                               .arg(column)
184                               .arg(message);
185         QMessageBox::information(nullptr, QObject::tr("Qt Linguist"), msg);
186     }
187     return false;
188 }
189 
PhraseBook()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 
~PhraseBook()199 PhraseBook::~PhraseBook()
200 {
201     qDeleteAll(m_phrases);
202 }
203 
setLanguageAndCountry(QLocale::Language lang,QLocale::Country country)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 
setSourceLanguageAndCountry(QLocale::Language lang,QLocale::Country country)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 
load(const QString & fileName,bool * langGuessed)222 bool PhraseBook::load(const QString &fileName, bool *langGuessed)
223 {
224     QFile f(fileName);
225     if (!f.open(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(hand->language(), &m_language, &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(lang, &m_sourceLanguage, &m_sourceCountry);
250     }
251 
252     delete hand;
253     f.close();
254     if (!ok) {
255         qDeleteAll(m_phrases);
256         m_phrases.clear();
257     } else {
258         emit listChanged();
259     }
260 
261     return ok;
262 }
263 
save(const QString & fileName)264 bool PhraseBook::save(const QString &fileName)
265 {
266     QFile f(fileName);
267     if (!f.open(QIODevice::WriteOnly))
268         return false;
269 
270     m_fileName = fileName;
271 
272     QTextStream t(&f);
273     t.setCodec( QTextCodec::codecForName("UTF-8") );
274 
275     t << "<!DOCTYPE QPH>\n<QPH";
276     if (sourceLanguage() != QLocale::C)
277         t << " sourcelanguage=\""
278           << Translator::makeLanguageCode(sourceLanguage(), sourceCountry()) << '"';
279     if (language() != QLocale::C)
280         t << " language=\"" << Translator::makeLanguageCode(language(), country()) << '"';
281     t << ">\n";
282     foreach (Phrase *p, m_phrases) {
283         t << "<phrase>\n";
284         t << "    <source>" << protect( p->source() ) << "</source>\n";
285         t << "    <target>" << protect( p->target() ) << "</target>\n";
286         if (!p->definition().isEmpty())
287             t << "    <definition>" << protect( 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 
append(Phrase * phrase)297 void PhraseBook::append(Phrase *phrase)
298 {
299     m_phrases.append(phrase);
300     phrase->setPhraseBook(this);
301     setModified(true);
302     emit listChanged();
303 }
304 
remove(Phrase * phrase)305 void PhraseBook::remove(Phrase *phrase)
306 {
307     m_phrases.removeOne(phrase);
308     phrase->setPhraseBook(0);
309     setModified(true);
310     emit listChanged();
311 }
312 
setModified(bool modified)313 void PhraseBook::setModified(bool modified)
314  {
315      if (m_changed != modified) {
316          emit modifiedChanged(modified);
317          m_changed = modified;
318      }
319 }
320 
phraseChanged(Phrase * p)321 void PhraseBook::phraseChanged(Phrase *p)
322 {
323     Q_UNUSED(p);
324 
325     setModified(true);
326 }
327 
friendlyPhraseBookName() const328 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