1 /*****************************************************************************
2 * stardict.cpp - QStarDict, a StarDict clone written using Qt *
3 * Copyright (C) 2008 Alexander Rodin *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License along *
16 * with this program; if not, write to the Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 *****************************************************************************/
19
20 #include "stardict.h"
21
22 #include <list>
23 #include <map>
24 #include <string>
25 #include <utility>
26 #include <QCoreApplication>
27 #include <QDir>
28 #include <QFile>
29 #include <QSettings>
30 #include <QStack>
31 #include <glib.h>
32 #include "lib.h"
33 #include "file.hpp"
34 #include "settingsdialog.h"
35 #include "../pluginserver.h"
36 #include <QDebug>
37 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
38 #include "stardict-meta.h"
39 #endif
40
41 namespace
42 {
43 void xdxf2html(QString &str);
44 QString whereDict(const QString &name, const QStringList &dictDirs);
45 const int MaxFuzzy = 24;
46
47 class StdList: public std::list<std::string>
48 {
49 public:
StdList()50 StdList()
51 : std::list<std::string>()
52 { }
53
StdList(const QList<QString> & list)54 StdList(const QList<QString> &list)
55 : std::list<std::string>()
56 {
57 for (QList<QString>::const_iterator i = list.begin(); i != list.end(); ++i)
58 push_back(i->toUtf8().data());
59 }
60
StdList(const std::list<std::string> & list)61 StdList(const std::list<std::string> &list)
62 : std::list<std::string>(list)
63 { }
64
toStringList() const65 QStringList toStringList() const
66 {
67 QStringList list;
68 for (const_iterator i = begin(); i != end(); ++i)
69 list << QString::fromUtf8(i->c_str());
70 return list;
71 }
72 };
73
74 class IfoListSetter
75 {
76 public:
IfoListSetter(QStringList * list)77 IfoListSetter(QStringList *list)
78 : m_list(list)
79 { }
80
operator ()(const std::string & filename,bool)81 void operator ()(const std::string &filename, bool)
82 {
83 DictInfo info;
84 if (info.load_from_ifo_file(filename, false))
85 m_list->push_back(QString::fromUtf8(info.bookname.c_str()));
86 }
87
88 private:
89 QStringList *m_list;
90 };
91
92 class IfoFileFinder
93 {
94 public:
IfoFileFinder(const QString & name,QString * filename)95 IfoFileFinder(const QString &name, QString *filename)
96 : m_name(name.toUtf8().data()),
97 m_filename(filename)
98 { }
99
operator ()(const std::string & filename,bool)100 void operator()(const std::string &filename, bool)
101 {
102 DictInfo info;
103 if (info.load_from_ifo_file(filename, false) && info.bookname == m_name) {
104 *m_filename = QString::fromUtf8(filename.c_str());
105 }
106 }
107
108 private:
109 std::string m_name;
110 QString *m_filename;
111 };
112 }
113
StarDict(QObject * parent)114 StarDict::StarDict(QObject *parent)
115 : QObject(parent)
116 {
117 m_sdLibs = new Libs;
118 QSettings settings("qstardict","qstardict");
119
120 m_dictDirs = settings.value("StarDict/dictDirs", m_dictDirs).toStringList();
121 m_reformatLists = settings.value("StarDict/reformatLists", true).toBool();
122 m_expandAbbreviations = settings.value("StarDict/expandAbbreviations", true).toBool();
123 if (m_dictDirs.isEmpty())
124 {
125 #ifdef Q_OS_UNIX
126 m_dictDirs << "/usr/local/share/stardict/dic";
127 #else
128 m_dictDirs << QCoreApplication::applicationDirPath() + "/dic";
129 #endif // Q_OS_UNIX
130 m_dictDirs << QDir::homePath() + "/.stardict/dic";
131 }
132 }
133
~StarDict()134 StarDict::~StarDict()
135 {
136 QSettings settings("qstardict","qstardict");
137 settings.setValue("StarDict/dictDirs", m_dictDirs);
138 settings.setValue("StarDict/reformatLists", m_reformatLists);
139 settings.setValue("StarDict/expandAbbreviations", m_expandAbbreviations);
140 delete m_sdLibs;
141 }
142
143 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
metadata() const144 QStarDict::PluginMetadata StarDict::metadata() const
145 {
146 QStarDict::PluginMetadata md;
147 md.id = PLUGIN_ID;
148 md.name = QString::fromUtf8(PLUGIN_NAME);
149 md.version = PLUGIN_VERSION;
150 md.description = PLUGIN_DESCRIPTION;
151 md.authors = QString::fromUtf8(PLUGIN_AUTHORS).split(';', QString::SkipEmptyParts);
152 md.features = QString::fromLatin1(PLUGIN_FEATURES).split(';', QString::SkipEmptyParts);
153 md.icon = QIcon(":/icons/logo.png");
154 return md;
155 }
156 #else
pluginIcon() const157 QIcon StarDict::pluginIcon() const
158 {
159 return QIcon(":/icons/logo.png");
160 }
161 #endif
162
availableDicts() const163 QStringList StarDict::availableDicts() const
164 {
165 QStringList result;
166 IfoListSetter setter(&result);
167 for_each_file(StdList(m_dictDirs), ".ifo", StdList(), StdList(), setter);
168
169 return result;
170 }
171
setLoadedDicts(const QStringList & loadedDicts)172 void StarDict::setLoadedDicts(const QStringList &loadedDicts)
173 {
174 QStringList available = availableDicts();
175 StdList disabled;
176 for (QStringList::const_iterator i = available.begin(); i != available.end(); ++i)
177 {
178 if (! loadedDicts.contains(*i))
179 disabled.push_back(i->toUtf8().data());
180 }
181 m_sdLibs->reload(StdList(m_dictDirs), StdList(loadedDicts), disabled);
182
183 m_loadedDicts.clear();
184 for (int i = 0; i < m_sdLibs->ndicts(); ++i)
185 m_loadedDicts[QString::fromUtf8(m_sdLibs->dict_name(i).c_str())] = i;
186 }
187
dictInfo(const QString & dict)188 StarDict::DictInfo StarDict::dictInfo(const QString &dict)
189 {
190 ::DictInfo nativeInfo;
191 nativeInfo.wordcount = 0;
192 if (! nativeInfo.load_from_ifo_file(whereDict(dict, m_dictDirs).toUtf8().data(), false)) {
193 return DictInfo();
194 }
195 QString pluginId = qsd->idForPlugin(this);
196 DictInfo result(pluginId, dict);
197 result.setAuthor(QString::fromUtf8(nativeInfo.author.c_str()));
198 result.setDescription(QString::fromUtf8(nativeInfo.description.c_str()));
199 result.setWordsCount(nativeInfo.wordcount ? static_cast<long>(nativeInfo.wordcount) : -1);
200 return result;
201 }
202
isTranslatable(const QString & dict,const QString & word)203 bool StarDict::isTranslatable(const QString &dict, const QString &word)
204 {
205 if (! m_loadedDicts.contains(dict))
206 return false;
207 long ind;
208 return m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]);
209 }
210
translate(const QString & dict,const QString & word)211 StarDict::Translation StarDict::translate(const QString &dict, const QString &word)
212 {
213 if (! m_loadedDicts.contains(dict))
214 return Translation();
215 if (word.isEmpty())
216 return Translation();
217 int dictIndex = m_loadedDicts[dict];
218 long ind;
219 if (! m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]))
220 return Translation();
221 return Translation(QString::fromUtf8(m_sdLibs->poGetWord(ind, dictIndex)),
222 QString::fromUtf8(m_sdLibs->dict_name(dictIndex).c_str()),
223 parseData(m_sdLibs->poGetWordData(ind, dictIndex), dictIndex, true,
224 m_reformatLists, m_expandAbbreviations));
225 }
226
findSimilarWords(const QString & dict,const QString & word)227 QStringList StarDict::findSimilarWords(const QString &dict, const QString &word)
228 {
229 if (! m_loadedDicts.contains(dict))
230 return QStringList();
231 gchar *fuzzy_res[MaxFuzzy];
232 if (! m_sdLibs->LookupWithFuzzy(word.toUtf8().data(), fuzzy_res, MaxFuzzy, m_loadedDicts[dict]))
233 return QStringList();
234 QStringList result;
235 for (gchar **p = fuzzy_res, **end = fuzzy_res + MaxFuzzy; p != end && *p; ++p)
236 {
237 result << QString::fromUtf8(*p);
238 g_free(*p);
239 }
240 return result;
241 }
242
execSettingsDialog(QWidget * parent)243 int StarDict::execSettingsDialog(QWidget *parent)
244 {
245 ::SettingsDialog dialog(this, parent);
246 return dialog.exec();
247 }
248
parseData(const char * data,int dictIndex,bool htmlSpaces,bool reformatLists,bool expandAbbreviations)249 QString StarDict::parseData(const char *data, int dictIndex, bool htmlSpaces, bool reformatLists, bool expandAbbreviations)
250 {
251 QString result;
252 quint32 dataSize = *reinterpret_cast<const quint32*>(data);
253 const char *dataEnd = data + dataSize;
254 const char *ptr = data + sizeof(quint32);
255 while (ptr < dataEnd)
256 {
257 switch (*ptr++)
258 {
259 case 'm':
260 case 'l':
261 case 'g':
262 {
263 QString str = QString::fromUtf8(ptr);
264 ptr += str.toUtf8().length() + 1;
265 result += str;
266 break;
267 }
268 case 'x':
269 {
270 QString str = QString::fromUtf8(ptr);
271 ptr += str.toUtf8().length() + 1;
272 xdxf2html(str);
273 result += str;
274 break;
275 }
276 case 't':
277 {
278 QString str = QString::fromUtf8(ptr);
279 ptr += str.toUtf8().length() + 1;
280 result += "<font class=\"example\">";
281 result += str;
282 result += "</font>";
283 break;
284 }
285 case 'y':
286 {
287 ptr += strlen(ptr) + 1;
288 break;
289 }
290 case 'W':
291 case 'P':
292 {
293 ptr += *reinterpret_cast<const quint32*>(ptr) + sizeof(quint32);
294 break;
295 }
296 default:
297 ; // nothing
298 }
299 }
300
301 if (expandAbbreviations)
302 {
303 QRegExp regExp("_\\S+[\\.:]");
304 int pos = 0;
305 while ((pos = regExp.indexIn(result, pos)) != -1)
306 {
307 long ind;
308 if (m_sdLibs->SimpleLookupWord(result.mid(pos, regExp.matchedLength()).toUtf8().data(), ind, dictIndex))
309 {
310 QString expanded = "<font class=\"explanation\">";
311 expanded += parseData(m_sdLibs->poGetWordData(ind, dictIndex));
312 if (result[pos + regExp.matchedLength() - 1] == ':')
313 expanded += ':';
314 expanded += "</font>";
315 result.replace(pos, regExp.matchedLength(), expanded);
316 pos += expanded.length();
317 }
318 else
319 pos += regExp.matchedLength();
320 }
321 }
322 if (reformatLists)
323 {
324 int pos = 0;
325 QStack<QChar> openedLists;
326 while (pos < result.length())
327 {
328 if (result[pos].isDigit())
329 {
330 int n = 0;
331 while (result[pos + n].isDigit())
332 ++n;
333 pos += n;
334 if (result[pos] == '&' && result.mid(pos + 1, 3) == "gt;")
335 result.replace(pos, 4, ">");
336 QChar marker = result[pos];
337 QString replacement;
338 if (marker == '>' || marker == '.' || marker == ')')
339 {
340 if (n == 1 && result[pos - 1] == '1') // open new list
341 {
342 if (openedLists.contains(marker))
343 {
344 replacement = "</li></ol>";
345 while (openedLists.size() && openedLists.top() != marker)
346 {
347 replacement += "</li></ol>";
348 openedLists.pop();
349 }
350 }
351 openedLists.push(marker);
352 replacement += "<ol>";
353 }
354 else
355 {
356 while (openedLists.size() && openedLists.top() != marker)
357 {
358 replacement += "</li></ol>";
359 openedLists.pop();
360 }
361 replacement += "</li>";
362 }
363 replacement += "<li>";
364 pos -= n;
365 n += pos;
366 while (result[pos - 1].isSpace())
367 --pos;
368 while (result[n + 1].isSpace())
369 ++n;
370 result.replace(pos, n - pos + 1, replacement);
371 pos += replacement.length();
372 }
373 else
374 ++pos;
375 }
376 else
377 ++pos;
378 }
379 while (openedLists.size())
380 {
381 result += "</li></ol>";
382 openedLists.pop();
383 }
384 }
385 if (htmlSpaces)
386 {
387 int n = 0;
388 while (result[n].isSpace())
389 ++n;
390 result.remove(0, n);
391 n = 0;
392 while (result[result.length() - 1 - n].isSpace())
393 ++n;
394 result.remove(result.length() - n, n);
395
396 for (int pos = 0; pos < result.length();)
397 {
398 switch (result[pos].toLatin1())
399 {
400 case '[':
401 result.insert(pos, "<font class=\"transcription\">");
402 pos += 28 + 1; // sizeof "<font class=\"transcription\">" + 1
403 break;
404 case ']':
405 result.insert(pos + 1, "</font>");
406 pos += 7 + 1; // sizeof "</font>" + 1
407 break;
408 case '\t':
409 result.insert(pos, " ");
410 pos += 24 + 1; // sizeof " " + 1
411 break;
412 case '\n':
413 {
414 int count = 1;
415 n = 1;
416 while (result[pos + n].isSpace())
417 {
418 if (result[pos + n] == '\n')
419 ++count;
420 ++n;
421 }
422 if (count > 1)
423 result.replace(pos, n, "</p><p>");
424 else
425 result.replace(pos, n, "<br>");
426 break;
427 }
428 default:
429 ++pos;
430 }
431 }
432 }
433 return result;
434 }
435
436 namespace
437 {
whereDict(const QString & name,const QStringList & dictDirs)438 QString whereDict(const QString &name, const QStringList &dictDirs)
439 {
440 QString filename;
441 IfoFileFinder finder(name, &filename);
442 for_each_file(StdList(dictDirs), ".ifo", StdList(), StdList(), finder);
443 return filename;
444 }
445
xdxf2html(QString & str)446 void xdxf2html(QString &str)
447 {
448 str.replace("<abr>", "<font class=\"abbreviature\">");
449 str.replace("<tr>", "<font class=\"transcription\">[");
450 str.replace("</tr>", "]</font>");
451 str.replace("<ex>", "<font class=\"example\">");
452 str.replace(QRegExp("<k>.*<\\/k>"), "");
453 str.replace(QRegExp("(<\\/abr>)|(<\\ex>)"), "</font");
454 }
455
456 }
457
458 #if QT_VERSION < 0x050000
459 Q_EXPORT_PLUGIN2(stardict, StarDict)
460 #endif
461
462 // vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc
463