06c877800d52cd01d6b7ce64858320137f05360a
[qstardict] / plugins / stardict / stardict.cpp
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/gmem.h>
32 #include <glib/gstrfuncs.h>
33 #include "lib.h"
34 #include "file.hpp"
35 #include "settingsdialog.h"
36 #include <QDebug>
37 namespace
38 {
39 void xdxf2html(QString &str);
40 QString whereDict(const QString &name, const QStringList &dictDirs);
41 const int MaxFuzzy = 24;
42
43 class StdList: public std::list<std::string>
44 {
45     public:
46         StdList()
47             : std::list<std::string>()
48         { }
49
50         StdList(const QList<QString> &list)
51             : std::list<std::string>()
52         {
53             for (QList<QString>::const_iterator i = list.begin(); i != list.end(); ++i)
54                 push_back(i->toUtf8().data());
55         }
56
57         StdList(const std::list<std::string> &list)
58             : std::list<std::string>(list)
59         { }
60
61         QStringList toStringList() const
62         {
63             QStringList list;
64             for (const_iterator i = begin(); i != end(); ++i)
65                 list << QString::fromUtf8(i->c_str());
66             return list;
67         }
68 };
69
70 class IfoListSetter
71 {
72     public:
73         IfoListSetter(QStringList *list)
74             : m_list(list)
75         { }
76
77         void operator ()(const std::string &filename, bool)
78         {
79             DictInfo info;
80             if (info.load_from_ifo_file(filename, false))
81                 m_list->push_back(QString::fromUtf8(info.bookname.c_str()));
82         }
83
84     private:
85         QStringList *m_list;
86 };
87
88 class IfoFileFinder
89 {
90     public:
91         IfoFileFinder(const QString &name, QString *filename)
92             : m_name(name.toUtf8().data()),
93               m_filename(filename)
94         { }
95
96         void operator()(const std::string &filename, bool)
97         {
98             DictInfo info;
99             if (info.load_from_ifo_file(filename, false) && info.bookname == m_name) {
100                 *m_filename = QString::fromUtf8(filename.c_str());
101             }
102         }
103
104     private:
105         std::string m_name;
106         QString *m_filename;
107 };
108 }
109
110 StarDict::StarDict(QObject *parent)
111     : QObject(parent)
112 {
113     m_sdLibs = new Libs;
114     QSettings settings("qstardict","qstardict");
115
116     m_dictDirs = settings.value("StarDict/dictDirs", m_dictDirs).toStringList();
117     m_reformatLists = settings.value("StarDict/reformatLists", true).toBool();
118     m_expandAbbreviations = settings.value("StarDict/expandAbbreviations", true).toBool();
119     if (m_dictDirs.isEmpty())
120     {
121 #ifdef Q_OS_UNIX
122         m_dictDirs << "/usr/share/stardict/dic";
123 #else
124         m_dictDirs << QCoreApplication::applicationDirPath() + "/dic";
125 #endif // Q_OS_UNIX                             
126         m_dictDirs << QDir::homePath() + "/.stardict/dic";
127     }
128 }
129
130 StarDict::~StarDict()
131 {
132     QSettings settings("qstardict","qstardict");
133     settings.setValue("StarDict/dictDirs", m_dictDirs);
134     settings.setValue("StarDict/reformatLists", m_reformatLists);
135     settings.setValue("StarDict/expandAbbreviations", m_expandAbbreviations);
136     delete m_sdLibs;
137 }
138
139 QStringList StarDict::availableDicts() const
140 {
141     QStringList result;
142     IfoListSetter setter(&result);
143     for_each_file(StdList(m_dictDirs), ".ifo", StdList(), StdList(), setter);
144
145     return result;
146 }
147
148 void StarDict::setLoadedDicts(const QStringList &loadedDicts)
149 {
150     QStringList available = availableDicts();
151     StdList disabled;
152     for (QStringList::const_iterator i = available.begin(); i != available.end(); ++i)
153     {
154         if (! loadedDicts.contains(*i))
155             disabled.push_back(i->toUtf8().data());
156     }
157     m_sdLibs->reload(StdList(m_dictDirs), StdList(loadedDicts), disabled);
158
159     m_loadedDicts.clear();
160     for (int i = 0; i < m_sdLibs->ndicts(); ++i)
161         m_loadedDicts[QString::fromUtf8(m_sdLibs->dict_name(i).c_str())] = i;
162 }
163
164 StarDict::DictInfo StarDict::dictInfo(const QString &dict)
165 {
166     ::DictInfo nativeInfo;
167     nativeInfo.wordcount = 0;
168     if (! nativeInfo.load_from_ifo_file(whereDict(dict, m_dictDirs).toUtf8().data(), false)) {
169         return DictInfo();
170     }
171     DictInfo result(name(), dict);
172     result.setAuthor(QString::fromUtf8(nativeInfo.author.c_str()));
173     result.setDescription(QString::fromUtf8(nativeInfo.description.c_str()));
174     result.setWordsCount(nativeInfo.wordcount ? static_cast<long>(nativeInfo.wordcount) : -1);
175     return result;
176 }
177
178 bool StarDict::isTranslatable(const QString &dict, const QString &word)
179 {
180     if (! m_loadedDicts.contains(dict))
181         return false;
182     long ind;
183     return m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]);
184 }
185
186 StarDict::Translation StarDict::translate(const QString &dict, const QString &word)
187 {
188     if (! m_loadedDicts.contains(dict))
189         return Translation();
190     if (word.isEmpty())
191         return Translation();
192     int dictIndex = m_loadedDicts[dict];
193     long ind;
194     if (! m_sdLibs->SimpleLookupWord(word.toUtf8().data(), ind, m_loadedDicts[dict]))
195         return Translation();
196     return Translation(QString::fromUtf8(m_sdLibs->poGetWord(ind, dictIndex)),
197             QString::fromUtf8(m_sdLibs->dict_name(dictIndex).c_str()),
198             parseData(m_sdLibs->poGetWordData(ind, dictIndex), dictIndex, true,
199                 m_reformatLists, m_expandAbbreviations));
200 }
201
202 QStringList StarDict::findSimilarWords(const QString &dict, const QString &word)
203 {
204     if (! m_loadedDicts.contains(dict))
205         return QStringList();
206     gchar *fuzzy_res[MaxFuzzy];
207     if (! m_sdLibs->LookupWithFuzzy(word.toUtf8().data(), fuzzy_res, MaxFuzzy, m_loadedDicts[dict]))
208         return QStringList();
209     QStringList result;
210     for (gchar **p = fuzzy_res, **end = fuzzy_res + MaxFuzzy; p != end && *p; ++p)
211     {
212         result << QString::fromUtf8(*p);
213         g_free(*p);
214     }
215     return result;
216 }
217
218 int StarDict::execSettingsDialog(QWidget *parent)
219 {
220     ::SettingsDialog dialog(this, parent);
221     return dialog.exec();
222 }
223
224 QString StarDict::parseData(const char *data, int dictIndex, bool htmlSpaces, bool reformatLists, bool expandAbbreviations)
225 {
226     QString result;
227     quint32 dataSize = *reinterpret_cast<const quint32*>(data);
228     const char *dataEnd = data + dataSize;
229     const char *ptr = data + sizeof(quint32);
230     while (ptr < dataEnd)
231     {
232         switch (*ptr++)
233         {
234             case 'm':
235             case 'l':
236             case 'g':
237             {
238                 QString str = QString::fromUtf8(ptr);
239                 ptr += str.toUtf8().length() + 1;
240                 result += str;
241                 break;
242             }
243             case 'x':
244             {
245                 QString str = QString::fromUtf8(ptr);
246                 ptr += str.toUtf8().length() + 1;
247                 xdxf2html(str);
248                 result += str;
249                 break;
250             }
251             case 't':
252             {
253                 QString str = QString::fromUtf8(ptr);
254                 ptr += str.toUtf8().length() + 1;
255                 result += "<font class=\"example\">";
256                 result += str;
257                 result += "</font>";
258                 break;
259             }
260             case 'y':
261             {
262                 ptr += strlen(ptr) + 1;
263                 break;
264             }
265             case 'W':
266             case 'P':
267             {
268                 ptr += *reinterpret_cast<const quint32*>(ptr) + sizeof(quint32);
269                 break;
270             }
271             default:
272                 ; // nothing
273         }
274     }
275
276     if (expandAbbreviations)
277     {
278         QRegExp regExp("_\\S+[\\.:]");
279         int pos = 0;
280         while ((pos = regExp.indexIn(result, pos)) != -1)
281         {
282             long ind;
283             if (m_sdLibs->SimpleLookupWord(result.mid(pos, regExp.matchedLength()).toUtf8().data(), ind, dictIndex))
284             {
285                 QString expanded = "<font class=\"explanation\">";
286                 expanded += parseData(m_sdLibs->poGetWordData(ind, dictIndex));
287                 if (result[pos + regExp.matchedLength() - 1] == ':')
288                     expanded += ':';
289                 expanded += "</font>";
290                 result.replace(pos, regExp.matchedLength(), expanded);
291                 pos += expanded.length();
292             }
293             else
294                 pos += regExp.matchedLength();
295         }
296     }
297     if (reformatLists)
298     {
299         int pos = 0;
300         QStack<QChar> openedLists;
301         while (pos < result.length())
302         {
303             if (result[pos].isDigit())
304             {
305                 int n = 0;
306                 while (result[pos + n].isDigit())
307                     ++n;
308                 pos += n;
309                 if (result[pos] == '&' && result.mid(pos + 1, 3) == "gt;")
310                     result.replace(pos, 4, ">");
311                 QChar marker = result[pos];
312                 QString replacement;
313                 if (marker == '>' || marker == '.' || marker == ')')
314                 {
315                     if (n == 1 && result[pos - 1] == '1') // open new list
316                     {
317                         if (openedLists.contains(marker))
318                         {
319                             replacement = "</li></ol>";
320                             while (openedLists.size() && openedLists.top() != marker)
321                             {
322                                 replacement += "</li></ol>";
323                                 openedLists.pop();
324                             }
325                         }
326                         openedLists.push(marker);
327                         replacement += "<ol>";
328                     }
329                     else
330                     {
331                         while (openedLists.size() && openedLists.top() != marker)
332                         {
333                             replacement += "</li></ol>";
334                             openedLists.pop();
335                         }
336                         replacement += "</li>";
337                     }
338                     replacement += "<li>";
339                     pos -= n;
340                     n += pos;
341                     while (result[pos - 1].isSpace())
342                         --pos;
343                     while (result[n + 1].isSpace())
344                         ++n;
345                     result.replace(pos, n - pos + 1, replacement);
346                     pos += replacement.length();
347                 }
348                 else
349                     ++pos;
350             }
351             else
352                 ++pos;
353         }
354         while (openedLists.size())
355         {
356             result += "</li></ol>";
357             openedLists.pop();
358         }
359     }
360     if (htmlSpaces)
361     {
362         int n = 0;
363         while (result[n].isSpace())
364             ++n;
365         result.remove(0, n);
366         n = 0;
367         while (result[result.length() - 1 - n].isSpace())
368             ++n;
369         result.remove(result.length() - n, n);
370
371         for (int pos = 0; pos < result.length();)
372         {
373             switch (result[pos].toAscii())
374             {
375                 case '[':
376                     result.insert(pos, "<font class=\"transcription\">");
377                     pos += 28 + 1; // sizeof "<font class=\"transcription\">" + 1
378                     break;
379                 case ']':
380                     result.insert(pos + 1, "</font>");
381                     pos += 7 + 1; // sizeof "</font>" + 1
382                     break;
383                 case '\t':
384                     result.insert(pos, "&nbsp;&nbsp;&nbsp;&nbsp;");
385                     pos += 24 + 1; // sizeof "&nbsp;&nbsp;&nbsp;&nbsp;" + 1
386                     break;
387                 case '\n':
388                 {
389                     int count = 1;
390                     n = 1;
391                     while (result[pos + n].isSpace())
392                     {
393                         if (result[pos + n] == '\n')
394                             ++count;
395                         ++n;
396                     }
397                     if (count > 1)
398                         result.replace(pos, n, "</p><p>");
399                     else
400                         result.replace(pos, n, "<br>");
401                     break;
402                 }
403                 default:
404                     ++pos;
405             }
406         }
407     }
408     return result;
409 }
410
411 namespace
412 {
413 QString whereDict(const QString &name, const QStringList &dictDirs)
414 {
415     QString filename;
416     IfoFileFinder finder(name, &filename);
417     for_each_file(StdList(dictDirs), ".ifo", StdList(), StdList(), finder);
418     return filename;
419 }
420
421 void xdxf2html(QString &str)
422 {
423     str.replace("<abr>", "<font class=\"abbreviature\">");
424     str.replace("<tr>", "<font class=\"transcription\">[");
425     str.replace("</tr>", "]</font>");
426     str.replace("<ex>", "<font class=\"example\">");
427     str.replace(QRegExp("<k>.*<\\/k>"), "");
428     str.replace(QRegExp("(<\\/abr>)|(<\\ex>)"), "</font");
429 }
430
431 }
432
433 Q_EXPORT_PLUGIN2(stardict, StarDict)
434
435 // vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc