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, "&nbsp;&nbsp;&nbsp;&nbsp;");
410 					pos += 24 + 1; // sizeof "&nbsp;&nbsp;&nbsp;&nbsp;" + 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