1 /*
2 * This file part of sdcv - console version of Stardict program
3 * http://sdcv.sourceforge.net
4 * Copyright (C) 2005-2006 Evgeniy <dushistov@mail.ru>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <cstring>
26 #include <map>
27 #include <memory>
28
29 #include <glib/gi18n.h>
30
31 #include "utils.hpp"
32
33 #include "libwrapper.hpp"
34
35 static const char ESC_BLUE[] = "\033[0;34m";
36 static const char ESC_END[] = "\033[0m";
37 static const char ESC_BOLD[] = "\033[1m";
38 static const char ESC_ITALIC[] = "\033[3m";
39 static const char ESC_LIGHT_GRAY[] = "\033[0;37m";
40 static const char ESC_GREEN[] = "\033[0;32m";
41
42 static const char *SEARCH_TERM_VISFMT = ESC_BOLD;
43 static const char *NAME_OF_DICT_VISFMT = ESC_BLUE;
44 static const char *TRANSCRIPTION_VISFMT = ESC_BOLD;
45 static const char *EXAMPLE_VISFMT = ESC_LIGHT_GRAY;
46 static const char *KREF_VISFMT = ESC_BOLD;
47 static const char *ABR_VISFMT = ESC_GREEN;
48
xdxf2text(const char * p,bool colorize_output)49 static std::string xdxf2text(const char *p, bool colorize_output)
50 {
51 std::string res;
52 for (; *p; ++p) {
53 if (*p != '<') {
54 if (g_str_has_prefix(p, ">")) {
55 res += ">";
56 p += 3;
57 } else if (g_str_has_prefix(p, "<")) {
58 res += "<";
59 p += 3;
60 } else if (g_str_has_prefix(p, "&")) {
61 res += "&";
62 p += 4;
63 } else if (g_str_has_prefix(p, """)) {
64 res += "\"";
65 p += 5;
66 } else if (g_str_has_prefix(p, "'")) {
67 res += "\'";
68 p += 5;
69 } else
70 res += *p;
71 continue;
72 }
73
74 const char *next = strchr(p, '>');
75 if (!next)
76 continue;
77
78 const std::string name(p + 1, next - p - 1);
79
80 if (name == "abr")
81 res += colorize_output ? ABR_VISFMT : "";
82 else if (name == "/abr")
83 res += colorize_output ? ESC_END : "";
84 else if (name == "k") {
85 const char *begin = next;
86 if ((next = strstr(begin, "</k>")) != nullptr)
87 next += sizeof("</k>") - 1 - 1;
88 else
89 next = begin;
90 } else if (name == "kref") {
91 res += colorize_output ? KREF_VISFMT : "";
92 } else if (name == "/kref") {
93 res += colorize_output ? ESC_END : "";
94 } else if (name == "b")
95 res += colorize_output ? ESC_BOLD : "";
96 else if (name == "/b")
97 res += colorize_output ? ESC_END : "";
98 else if (name == "i")
99 res += colorize_output ? ESC_ITALIC : "";
100 else if (name == "/i")
101 res += colorize_output ? ESC_END : "";
102 else if (name == "tr") {
103 if (colorize_output)
104 res += TRANSCRIPTION_VISFMT;
105 res += "[";
106 } else if (name == "/tr") {
107 res += "]";
108 if (colorize_output)
109 res += ESC_END;
110 } else if (name == "ex")
111 res += colorize_output ? EXAMPLE_VISFMT : "";
112 else if (name == "/ex")
113 res += colorize_output ? ESC_END : "";
114 else if (!name.empty() && name[0] == 'c' && name != "co") {
115 std::string::size_type pos = name.find("code");
116 if (pos != std::string::npos) {
117 pos += sizeof("code=\"") - 1;
118 std::string::size_type end_pos = name.find("\"");
119 const std::string color(name, pos, end_pos - pos);
120 res += "";
121 } else {
122 res += "";
123 }
124 } else if (name == "/c")
125 res += "";
126
127 p = next;
128 }
129 return res;
130 }
131
parse_data(const gchar * data,bool colorize_output)132 static std::string parse_data(const gchar *data, bool colorize_output)
133 {
134 if (!data)
135 return "";
136
137 std::string res;
138 guint32 data_size, sec_size = 0;
139 gchar *m_str;
140 const gchar *p = data;
141 data_size = get_uint32(p);
142 p += sizeof(guint32);
143 while (guint32(p - data) < data_size) {
144 switch (*p++) {
145 case 'h': // HTML data
146 case 'w': // WikiMedia markup data
147 case 'm': // plain text, utf-8
148 case 'l': // not utf-8, some other locale encoding, discouraged, need more work...
149 sec_size = strlen(p);
150 if (sec_size) {
151 res += "\n";
152 m_str = g_strndup(p, sec_size);
153 res += m_str;
154 g_free(m_str);
155 }
156 sec_size++;
157 break;
158 case 'g': // pango markup data
159 case 'x': // xdxf
160 sec_size = strlen(p);
161 if (sec_size) {
162 res += "\n";
163 m_str = g_strndup(p, sec_size);
164 res += xdxf2text(m_str, colorize_output);
165 g_free(m_str);
166 }
167 sec_size++;
168 break;
169 case 't': // english phonetic string
170 sec_size = strlen(p);
171 if (sec_size) {
172 res += "\n";
173 if (colorize_output)
174 res += TRANSCRIPTION_VISFMT;
175 res += "[" + std::string(p, sec_size) + "]";
176 if (colorize_output)
177 res += ESC_END;
178 }
179 sec_size++;
180 break;
181 case 'k': // KingSoft PowerWord data
182 case 'y': // chinese YinBiao or japanese kana, utf-8
183 sec_size = strlen(p);
184 if (sec_size)
185 res += std::string(p, sec_size);
186 sec_size++;
187 break;
188 case 'W': // wav file
189 case 'P': // picture data
190 sec_size = get_uint32(p);
191 sec_size += sizeof(guint32);
192 break;
193 }
194 p += sec_size;
195 }
196
197 return res;
198 }
199
SimpleLookup(const std::string & str,TSearchResultList & res_list)200 void Library::SimpleLookup(const std::string &str, TSearchResultList &res_list)
201 {
202 glong ind;
203 res_list.reserve(ndicts());
204 for (gint idict = 0; idict < ndicts(); ++idict)
205 if (SimpleLookupWord(str.c_str(), ind, idict))
206 res_list.push_back(
207 TSearchResult(dict_name(idict),
208 poGetWord(ind, idict),
209 parse_data(poGetWordData(ind, idict), colorize_output_)));
210 }
211
LookupWithFuzzy(const std::string & str,TSearchResultList & res_list)212 void Library::LookupWithFuzzy(const std::string &str, TSearchResultList &res_list)
213 {
214 static const int MAXFUZZY = 10;
215
216 gchar *fuzzy_res[MAXFUZZY];
217 if (!Libs::LookupWithFuzzy(str.c_str(), fuzzy_res, MAXFUZZY))
218 return;
219
220 for (gchar **p = fuzzy_res, **end = (fuzzy_res + MAXFUZZY); p != end && *p; ++p) {
221 SimpleLookup(*p, res_list);
222 g_free(*p);
223 }
224 }
225
LookupWithRule(const std::string & str,TSearchResultList & res_list)226 void Library::LookupWithRule(const std::string &str, TSearchResultList &res_list)
227 {
228 std::vector<gchar *> match_res((MAX_MATCH_ITEM_PER_LIB)*ndicts());
229
230 const gint nfound = Libs::LookupWithRule(str.c_str(), &match_res[0]);
231 if (nfound == 0)
232 return;
233
234 for (gint i = 0; i < nfound; ++i) {
235 SimpleLookup(match_res[i], res_list);
236 g_free(match_res[i]);
237 }
238 }
239
LookupData(const std::string & str,TSearchResultList & res_list)240 void Library::LookupData(const std::string &str, TSearchResultList &res_list)
241 {
242 std::vector<std::vector<gchar *>> drl(ndicts());
243 if (!Libs::LookupData(str.c_str(), &drl[0]))
244 return;
245 for (int idict = 0; idict < ndicts(); ++idict)
246 for (gchar *res : drl[idict]) {
247 SimpleLookup(res, res_list);
248 g_free(res);
249 }
250 }
251
print_search_result(FILE * out,const TSearchResult & res,bool & first_result)252 void Library::print_search_result(FILE *out, const TSearchResult &res, bool &first_result)
253 {
254 std::string loc_bookname, loc_def, loc_exp;
255
256 if (!utf8_output_) {
257 loc_bookname = utf8_to_locale_ign_err(res.bookname);
258 loc_def = utf8_to_locale_ign_err(res.def);
259 loc_exp = utf8_to_locale_ign_err(res.exp);
260 }
261 if (json_) {
262 if (!first_result) {
263 fputs(",", out);
264 } else {
265 first_result = false;
266 }
267 fprintf(out, "{\"dict\": \"%s\",\"word\":\"%s\",\"definition\":\"%s\"}",
268 json_escape_string(res.bookname).c_str(),
269 json_escape_string(res.def).c_str(),
270 json_escape_string(res.exp).c_str());
271
272 } else {
273 fprintf(out,
274 "-->%s%s%s\n"
275 "-->%s%s%s\n"
276 "%s\n\n",
277 colorize_output_ ? NAME_OF_DICT_VISFMT : "",
278 utf8_output_ ? res.bookname.c_str() : loc_bookname.c_str(),
279 colorize_output_ ? ESC_END : "",
280 colorize_output_ ? SEARCH_TERM_VISFMT : "",
281 utf8_output_ ? res.def.c_str() : loc_def.c_str(),
282 colorize_output_ ? ESC_END : "",
283 utf8_output_ ? res.exp.c_str() : loc_exp.c_str());
284 }
285 }
286
287 namespace
288 {
289 class sdcv_pager final
290 {
291 public:
sdcv_pager(bool ignore_env=false)292 explicit sdcv_pager(bool ignore_env = false)
293 {
294 output = stdout;
295 if (ignore_env) {
296 return;
297 }
298 const gchar *pager = g_getenv("SDCV_PAGER");
299 if (pager && (output = popen(pager, "w")) == nullptr) {
300 perror(_("popen failed"));
301 output = stdout;
302 }
303 }
304 sdcv_pager(const sdcv_pager &) = delete;
305 sdcv_pager &operator=(const sdcv_pager &) = delete;
~sdcv_pager()306 ~sdcv_pager()
307 {
308 if (output != stdout) {
309 pclose(output);
310 }
311 }
get_stream()312 FILE *get_stream() { return output; }
313
314 private:
315 FILE *output;
316 };
317 }
318
process_phrase(const char * loc_str,IReadLine & io,bool force)319 bool Library::process_phrase(const char *loc_str, IReadLine &io, bool force)
320 {
321 if (nullptr == loc_str)
322 return true;
323
324 std::string query;
325
326 analyze_query(loc_str, query);
327 if (!query.empty())
328 io.add_to_history(query.c_str());
329
330 gsize bytes_read;
331 gsize bytes_written;
332 glib::Error err;
333 glib::CharStr str;
334 if (!utf8_input_)
335 str.reset(g_locale_to_utf8(loc_str, -1, &bytes_read, &bytes_written, get_addr(err)));
336 else
337 str.reset(g_strdup(loc_str));
338
339 if (nullptr == get_impl(str)) {
340 fprintf(stderr, _("Can not convert %s to utf8.\n"), loc_str);
341 fprintf(stderr, "%s\n", err->message);
342 return false;
343 }
344
345 if (str[0] == '\0')
346 return true;
347
348 TSearchResultList res_list;
349
350 switch (analyze_query(get_impl(str), query)) {
351 case qtFUZZY:
352 LookupWithFuzzy(query, res_list);
353 break;
354 case qtREGEXP:
355 LookupWithRule(query, res_list);
356 break;
357 case qtSIMPLE:
358 SimpleLookup(get_impl(str), res_list);
359 if (res_list.empty() && fuzzy_)
360 LookupWithFuzzy(get_impl(str), res_list);
361 break;
362 case qtDATA:
363 LookupData(query, res_list);
364 break;
365 default:
366 /*nothing*/;
367 }
368
369 bool first_result = true;
370 if (json_) {
371 fputc('[', stdout);
372 }
373 if (!res_list.empty()) {
374 /* try to be more clever, if there are
375 one or zero results per dictionary show all
376 */
377 bool show_all_results = true;
378 typedef std::map<std::string, int, std::less<std::string>> DictResMap;
379 if (!force) {
380 DictResMap res_per_dict;
381 for (const TSearchResult &search_res : res_list) {
382 auto r = res_per_dict.equal_range(search_res.bookname);
383 DictResMap tmp(r.first, r.second);
384 if (tmp.empty()) //there are no yet such bookname in map
385 res_per_dict.insert(DictResMap::value_type(search_res.bookname, 1));
386 else {
387 ++((tmp.begin())->second);
388 if (tmp.begin()->second > 1) {
389 show_all_results = false;
390 break;
391 }
392 }
393 }
394 } //if (!force)
395
396 if (!show_all_results && !force) {
397 if (!json_) {
398 printf(_("Found %zu items, similar to %s.\n"), res_list.size(),
399 utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
400 }
401 for (size_t i = 0; i < res_list.size(); ++i) {
402 const std::string loc_bookname = utf8_to_locale_ign_err(res_list[i].bookname);
403 const std::string loc_def = utf8_to_locale_ign_err(res_list[i].def);
404 printf("%zu)%s%s%s-->%s%s%s\n", i,
405 colorize_output_ ? NAME_OF_DICT_VISFMT : "",
406 utf8_output_ ? res_list[i].bookname.c_str() : loc_bookname.c_str(),
407 colorize_output_ ? ESC_END : "",
408 colorize_output_ ? SEARCH_TERM_VISFMT : "",
409 utf8_output_ ? res_list[i].def.c_str() : loc_def.c_str(),
410 colorize_output_ ? ESC_END : "");
411 }
412 int choise;
413 std::unique_ptr<IReadLine> choice_readline(create_readline_object());
414 for (;;) {
415 std::string str_choise;
416 choice_readline->read(_("Your choice[-1 to abort]: "), str_choise);
417 sscanf(str_choise.c_str(), "%d", &choise);
418 if (choise >= 0 && choise < int(res_list.size())) {
419 sdcv_pager pager;
420 io.add_to_history(res_list[choise].def.c_str());
421 print_search_result(pager.get_stream(), res_list[choise], first_result);
422 break;
423 } else if (choise == -1) {
424 break;
425 } else
426 printf(_("Invalid choice.\nIt must be from 0 to %zu or -1.\n"),
427 res_list.size() - 1);
428 }
429 } else {
430 sdcv_pager pager(force || json_);
431 if (!json_) {
432 fprintf(pager.get_stream(), _("Found %zu items, similar to %s.\n"),
433 res_list.size(), utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
434 }
435 for (const TSearchResult &search_res : res_list) {
436 print_search_result(pager.get_stream(), search_res, first_result);
437 }
438 }
439
440 } else {
441 std::string loc_str;
442 if (!utf8_output_)
443 loc_str = utf8_to_locale_ign_err(get_impl(str));
444 if (!json_)
445 printf(_("Nothing similar to %s, sorry :(\n"), utf8_output_ ? get_impl(str) : loc_str.c_str());
446 }
447
448 if (json_) {
449 fputs("]\n", stdout);
450 }
451 return true;
452 }
453