/* * A tool to query multiple dictionaries for the specified word * * Intended for use in IRC bots and similar silly things---words go in, * one per each line, and entries come out, one dictionary at a time, * finalised with an empty line. Newlines are escaped with `\n', * backslashes with `\\'. * * So far only the `m', `g`, and `x` fields are supported, as in sdtui. * * Copyright (c) 2013 - 2021, Přemysl Eric Janouch * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include #include #include "stardict.h" #include "stardict-private.h" #include "generator.h" #include "utils.h" // --- Output formatting ------------------------------------------------------- /// Transform Pango attributes to in-line formatting sequences (non-reentrant) typedef const gchar *(*FormatterFunc) (PangoAttrIterator *); static const gchar * pango_attrs_ignore (G_GNUC_UNUSED PangoAttrIterator *iterator) { return ""; } static const gchar * pango_attrs_to_irc (PangoAttrIterator *iterator) { static gchar buf[5]; gchar *p = buf; *p++ = 0x0f; if (!iterator) goto reset_formatting; PangoAttrInt *attr = NULL; if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_WEIGHT)) && attr->value >= PANGO_WEIGHT_BOLD) *p++ = 0x02; if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_UNDERLINE)) && attr->value == PANGO_UNDERLINE_SINGLE) *p++ = 0x1f; if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_STYLE)) && attr->value == PANGO_STYLE_ITALIC) *p++ = 0x1d; reset_formatting: *p++ = 0; return buf; } static const gchar * pango_attrs_to_ansi (PangoAttrIterator *iterator) { static gchar buf[16]; g_strlcpy (buf, "\x1b[0", sizeof buf); if (!iterator) goto reset_formatting; PangoAttrInt *attr = NULL; if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_WEIGHT)) && attr->value >= PANGO_WEIGHT_BOLD) g_strlcat (buf, ";1", sizeof buf); if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_UNDERLINE)) && attr->value == PANGO_UNDERLINE_SINGLE) g_strlcat (buf, ";4", sizeof buf); if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator, PANGO_ATTR_STYLE)) && attr->value == PANGO_STYLE_ITALIC) g_strlcat (buf, ";3", sizeof buf); reset_formatting: g_strlcat (buf, "m", sizeof buf); return buf; } static gchar * pango_to_output_text (const gchar *markup, FormatterFunc formatter) { // This function skips leading whitespace, but it's the canonical one gchar *text = NULL; PangoAttrList *attrs = NULL; if (!pango_parse_markup (markup, -1, 0, &attrs, &text, NULL, NULL)) return g_strdup_printf ("<%s>", ("error in entry")); PangoAttrIterator *iterator = pango_attr_list_get_iterator (attrs); GString *result = g_string_new (""); do { gint start = 0, end = 0; pango_attr_iterator_range (iterator, &start, &end); if (end == G_MAXINT) end = strlen (text); g_string_append (result, formatter (iterator)); g_string_append_len (result, text + start, end - start); } while (pango_attr_iterator_next (iterator)); g_string_append (result, formatter (NULL)); g_free (text); pango_attr_iterator_destroy (iterator); pango_attr_list_unref (attrs); return g_string_free (result, FALSE); } static gchar * field_to_output_text (const StardictEntryField *field, FormatterFunc formatter) { const gchar *definition = field->data; if (field->type == STARDICT_FIELD_MEANING) return g_strdup (definition); if (field->type == STARDICT_FIELD_PANGO) return pango_to_output_text (definition, formatter); if (field->type == STARDICT_FIELD_XDXF) { gchar *markup = xdxf_to_pango_markup_with_reduced_effort (definition); gchar *result = pango_to_output_text (markup, formatter); g_free (markup); return result; } return NULL; } // --- Main -------------------------------------------------------------------- static guint count_equal_chars (const gchar *a, const gchar *b) { guint count = 0; while (*a && *b) if (*a++ == *b++) count++; return count; } static void do_dictionary (StardictDict *dict, const gchar *word, FormatterFunc formatter) { gboolean found; StardictIterator *iter = stardict_dict_search (dict, word, &found); if (!found) goto out; // Default Stardict ordering is ASCII case-insensitive, // which may be further exacerbated by our own collation feature. // Try to find a better matching entry: gint64 best_offset = stardict_iterator_get_offset (iter); guint best_score = count_equal_chars (stardict_iterator_get_word (iter), word); while (TRUE) { stardict_iterator_next (iter); if (!stardict_iterator_is_valid (iter)) break; const gchar *iter_word = stardict_iterator_get_word (iter); if (g_ascii_strcasecmp (iter_word, word)) break; guint score = count_equal_chars (iter_word, word); if (score > best_score) { best_offset = stardict_iterator_get_offset (iter); best_score = score; } } stardict_iterator_set_offset (iter, best_offset, FALSE); StardictEntry *entry = stardict_iterator_get_entry (iter); StardictInfo *info = stardict_dict_get_info (dict); const GList *list = stardict_entry_get_fields (entry); for (; list; list = list->next) { StardictEntryField *field = list->data; gchar *definitions = field_to_output_text (field, formatter); if (!definitions) continue; printf ("%s\t", info->book_name); for (const gchar *p = definitions; *p; p++) { if (*p == '\\') printf ("\\\\"); else if (*p == '\n') printf ("\\n"); else putchar (*p); } putchar ('\n'); g_free (definitions); } g_object_unref (entry); out: g_object_unref (iter); } static FormatterFunc parse_options (int *argc, char ***argv) { GError *error = NULL; GOptionContext *ctx = g_option_context_new ("DICTIONARY.ifo... - query multiple dictionaries"); gboolean format_with_ansi = FALSE; gboolean format_with_irc = FALSE; GOptionEntry entries[] = { { "ansi", 'a', 0, G_OPTION_ARG_NONE, &format_with_ansi, "Format with ANSI sequences", NULL }, { "irc", 'i', 0, G_OPTION_ARG_NONE, &format_with_irc, "Format with IRC codes", NULL }, { } }; g_option_context_add_main_entries (ctx, entries, NULL); if (!g_option_context_parse (ctx, argc, argv, &error)) { g_printerr ("Error: option parsing failed: %s\n", error->message); exit (EXIT_FAILURE); } if (*argc < 2) { g_printerr ("%s\n", g_option_context_get_help (ctx, TRUE, NULL)); exit (EXIT_FAILURE); } g_option_context_free (ctx); if (format_with_ansi) return pango_attrs_to_ansi; if (format_with_irc) return pango_attrs_to_irc; return pango_attrs_ignore; } int main (int argc, char *argv[]) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS if (glib_check_version (2, 36, 0)) g_type_init (); G_GNUC_END_IGNORE_DEPRECATIONS FormatterFunc formatter = parse_options (&argc, &argv); guint n_dicts = argc - 1; StardictDict **dicts = g_alloca (sizeof *dicts * n_dicts); guint i; for (i = 1; i <= n_dicts; i++) { GError *error = NULL; dicts[i - 1] = stardict_dict_new (argv[i], &error); if (error) { g_printerr ("Error: opening dictionary `%s' failed: %s\n", argv[i], error->message); exit (EXIT_FAILURE); } } gint c; do { GString *s = g_string_new (NULL); while ((c = getchar ()) != EOF && c != '\n') if (c != '\r') g_string_append_c (s, c); if (s->len) for (i = 0; i < n_dicts; i++) do_dictionary (dicts[i], s->str, formatter); printf ("\n"); fflush (NULL); g_string_free (s, TRUE); } while (c != EOF); for (i = 0; i < n_dicts; i++) g_object_unref (dicts[i]); return 0; }