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, "&gt;")) {
55                 res += ">";
56                 p += 3;
57             } else if (g_str_has_prefix(p, "&lt;")) {
58                 res += "<";
59                 p += 3;
60             } else if (g_str_has_prefix(p, "&amp;")) {
61                 res += "&";
62                 p += 4;
63             } else if (g_str_has_prefix(p, "&quot;")) {
64                 res += "\"";
65                 p += 5;
66             } else if (g_str_has_prefix(p, "&apos;")) {
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