Move sources into their own directory
This commit is contained in:
262
src/add-pronunciation.c
Normal file
262
src/add-pronunciation.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* A tool to add eSpeak-generated pronunciation to dictionaries
|
||||
*
|
||||
* Here I use the `espeak' process rather than libespeak because of the GPL.
|
||||
*
|
||||
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "stardict.h"
|
||||
|
||||
|
||||
// --- Pronunciation generator -------------------------------------------------
|
||||
|
||||
typedef struct worker_data WorkerData;
|
||||
|
||||
struct worker_data
|
||||
{
|
||||
guint32 start_entry; //! The first entry to be processed
|
||||
guint32 end_entry; //! Past the last entry to be processed
|
||||
|
||||
/* Reader, writer */
|
||||
GMutex *dict_mutex; //! Locks the dictionary object
|
||||
|
||||
/* Reader */
|
||||
GThread *main_thread; //! A handle to the reader thread
|
||||
StardictDict *dict; //! The dictionary object
|
||||
gpointer output; //! Linked-list of pronunciation data
|
||||
|
||||
GMutex *remaining_mutex; //! Locks the progress stats
|
||||
GCond *remaining_cond; //! Signals a change in progress
|
||||
guint32 remaining; //! How many entries remain
|
||||
|
||||
/* Writer */
|
||||
StardictIterator *iterator; //! Iterates over the dictionary
|
||||
FILE *child_stdin; //! Standard input of eSpeak
|
||||
};
|
||||
|
||||
/** Writes to espeak's stdin. */
|
||||
static gpointer
|
||||
worker_writer (WorkerData *data)
|
||||
{
|
||||
while (stardict_iterator_get_offset (data->iterator) != data->end_entry)
|
||||
{
|
||||
g_mutex_lock (data->dict_mutex);
|
||||
const gchar *word = stardict_iterator_get_word (data->iterator);
|
||||
g_mutex_unlock (data->dict_mutex);
|
||||
|
||||
stardict_iterator_next (data->iterator);
|
||||
if (fprintf (data->child_stdin, "%s\n", word) < 0)
|
||||
g_error ("write to eSpeak failed: %s", strerror (errno));
|
||||
}
|
||||
|
||||
g_object_unref (data->iterator);
|
||||
return GINT_TO_POINTER (fclose (data->child_stdin));
|
||||
}
|
||||
|
||||
/** Reads from espeak's stdout. */
|
||||
static gpointer
|
||||
worker (WorkerData *data)
|
||||
{
|
||||
/* Spawn eSpeak */
|
||||
static gchar *cmdline[] = { "espeak", "--ipa", "-q", NULL };
|
||||
gint child_in, child_out;
|
||||
|
||||
GError *error;
|
||||
if (!g_spawn_async_with_pipes (NULL, cmdline, NULL,
|
||||
G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, &child_in, &child_out, NULL, &error))
|
||||
g_error ("g_spawn() failed: %s", error->message);
|
||||
|
||||
data->child_stdin = fdopen (child_in, "wb");
|
||||
if (!data->child_stdin)
|
||||
perror ("fdopen");
|
||||
|
||||
FILE *child_stdout = fdopen (child_out, "rb");
|
||||
if (!child_stdout)
|
||||
perror ("fdopen");
|
||||
|
||||
/* Spawn a writer thread */
|
||||
g_mutex_lock (data->dict_mutex);
|
||||
data->iterator = stardict_iterator_new (data->dict, data->start_entry);
|
||||
g_mutex_unlock (data->dict_mutex);
|
||||
|
||||
GThread *writer = g_thread_new ("write worker",
|
||||
(GThreadFunc) worker_writer, data);
|
||||
|
||||
/* Read the output */
|
||||
g_mutex_lock (data->remaining_mutex);
|
||||
guint32 remaining = data->remaining;
|
||||
g_mutex_unlock (data->remaining_mutex);
|
||||
|
||||
data->output = NULL;
|
||||
gpointer *output_end = &data->output;
|
||||
while (remaining)
|
||||
{
|
||||
static gchar next[sizeof (gpointer)];
|
||||
GString *s = g_string_new (NULL);
|
||||
g_string_append_len (s, next, sizeof next);
|
||||
|
||||
gint c;
|
||||
while ((c = fgetc (child_stdout)) != EOF && c != '\n')
|
||||
g_string_append_c (s, c);
|
||||
if (c == EOF)
|
||||
g_error ("eSpeak process died too soon");
|
||||
|
||||
gchar *translation = g_string_free (s, FALSE);
|
||||
*output_end = translation;
|
||||
output_end = (gpointer *) translation;
|
||||
|
||||
/* We limit progress reporting so that
|
||||
* the mutex doesn't spin like crazy */
|
||||
if ((--remaining & 1023) != 0)
|
||||
continue;
|
||||
|
||||
g_mutex_lock (data->remaining_mutex);
|
||||
data->remaining = remaining;
|
||||
g_cond_broadcast (data->remaining_cond);
|
||||
g_mutex_unlock (data->remaining_mutex);
|
||||
}
|
||||
|
||||
fclose (child_stdout);
|
||||
return g_thread_join (writer);
|
||||
}
|
||||
|
||||
// --- Main --------------------------------------------------------------------
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
gint n_processes = 1;
|
||||
|
||||
GOptionEntry entries[] =
|
||||
{
|
||||
{ "processes", 'N', G_OPTION_FLAG_IN_MAIN,
|
||||
G_OPTION_ARG_INT, &n_processes,
|
||||
"the number of espeak processes run in parallel", "PROCESSES" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
GError *error = NULL;
|
||||
GOptionContext *ctx = g_option_context_new
|
||||
("input.ifo output.ifo - add pronunciation to dictionaries");
|
||||
g_option_context_add_main_entries (ctx, entries, NULL);
|
||||
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||
{
|
||||
g_print ("option parsing failed: %s\n", error->message);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (argc != 3)
|
||||
{
|
||||
gchar *help = g_option_context_get_help (ctx, TRUE, FALSE);
|
||||
g_print ("%s", help);
|
||||
g_free (help);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
StardictDict *dict = stardict_dict_new (argv[1], &error);
|
||||
if (!dict)
|
||||
{
|
||||
g_printerr ("opening the dictionary failed: %s\n", error->message);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
gsize n_words = stardict_info_get_word_count
|
||||
(stardict_dict_get_info (dict));
|
||||
|
||||
if (n_processes <= 0)
|
||||
{
|
||||
g_printerr ("Error: there must be at least one process\n");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if ((gsize) n_processes > n_words * 1024)
|
||||
{
|
||||
n_processes = n_words / 1024;
|
||||
if (!n_processes)
|
||||
n_processes = 1;
|
||||
g_printerr ("Warning: too many processes, reducing to %d\n",
|
||||
n_processes);
|
||||
}
|
||||
|
||||
/* Spawn worker threads to generate pronunciations */
|
||||
static GMutex dict_mutex;
|
||||
|
||||
static GMutex remaining_mutex;
|
||||
static GCond remaining_cond;
|
||||
|
||||
WorkerData *data = g_alloca (sizeof *data * n_processes);
|
||||
|
||||
gint i;
|
||||
for (i = 0; i < n_processes; i++)
|
||||
{
|
||||
data[i].start_entry = (n_words - 1) * i / n_processes;
|
||||
data[i].end_entry = (n_words - 1) * (i + 1) / n_processes;
|
||||
|
||||
data[i].remaining = data[i].end_entry - data[i].start_entry;
|
||||
data[i].remaining_mutex = &remaining_mutex;
|
||||
data[i].remaining_cond = &remaining_cond;
|
||||
|
||||
data[i].dict = dict;
|
||||
data[i].dict_mutex = &dict_mutex;
|
||||
|
||||
data->main_thread = g_thread_new ("worker", (GThreadFunc) worker, data);
|
||||
}
|
||||
|
||||
/* Loop while the threads still have some work to do and report status */
|
||||
g_mutex_lock (&remaining_mutex);
|
||||
for (;;)
|
||||
{
|
||||
gboolean all_finished = TRUE;
|
||||
printf ("\rRetrieving pronunciation... ");
|
||||
for (i = 0; i < n_processes; i++)
|
||||
{
|
||||
printf ("%3u%% ", data[i].remaining * 100
|
||||
/ (data[i].end_entry - data[i].start_entry));
|
||||
if (data[i].remaining)
|
||||
all_finished = FALSE;
|
||||
}
|
||||
|
||||
if (all_finished)
|
||||
break;
|
||||
g_cond_wait (&remaining_cond, &remaining_mutex);
|
||||
}
|
||||
g_mutex_unlock (&remaining_mutex);
|
||||
|
||||
for (i = 0; i < n_processes; i++)
|
||||
g_thread_join (data[i].main_thread);
|
||||
|
||||
// TODO after all processing is done, the program will go through the whole
|
||||
// dictionary and put extended data entries into a new one.
|
||||
StardictIterator *iterator = stardict_iterator_new (dict, 0);
|
||||
while (stardict_iterator_is_valid (iterator))
|
||||
{
|
||||
// ...
|
||||
stardict_iterator_next (iterator);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
274
src/sdcli.c
Normal file
274
src/sdcli.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* StarDict console UI
|
||||
*
|
||||
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _XOPEN_SOURCE_EXTENDED /**< Yes, we want ncursesw. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <ncurses.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "stardict.h"
|
||||
|
||||
|
||||
#define KEY_ESCAPE 27 /**< Curses doesn't define this. */
|
||||
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
static void
|
||||
display (const gchar *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start (ap, format);
|
||||
vw_printw (stdscr, format, ap);
|
||||
va_end (ap);
|
||||
refresh ();
|
||||
}
|
||||
|
||||
static gchar *
|
||||
wchar_to_mb (wchar_t ch)
|
||||
{
|
||||
/* Convert the character back to a multi-byte sequence. */
|
||||
static gchar buffer[MB_LEN_MAX + 1];
|
||||
size_t len = wcrtomb (buffer, ch, NULL);
|
||||
|
||||
/* This shouldn't happen. It would mean that the user has
|
||||
* somehow managed to enter something inexpressable in the
|
||||
* current locale. */
|
||||
if (len == (size_t) -1)
|
||||
abort ();
|
||||
|
||||
/* Here I hope the buffer doesn't overflow. Who uses
|
||||
* shift states nowadays, anyway? */
|
||||
if (wcrtomb (buffer + len, L'\0', NULL) == (size_t) -1)
|
||||
abort ();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
wchar_to_mb_escaped (wchar_t ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case L'\r': return "\\r";
|
||||
case L'\n': return "\\n";
|
||||
case L'\t': return "\\t";
|
||||
default: return wchar_to_mb (ch);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
poll_restart (struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
int ret;
|
||||
do
|
||||
ret = poll (fds, nfds, timeout);
|
||||
while (ret == -1 && errno == EINTR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// --- SIGWINCH ----------------------------------------------------------------
|
||||
|
||||
static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */
|
||||
static void (*g_old_winch_handler) (int);
|
||||
|
||||
static void
|
||||
winch_handler (int signum)
|
||||
{
|
||||
/* Call the ncurses handler. */
|
||||
if (g_old_winch_handler)
|
||||
g_old_winch_handler (signum);
|
||||
|
||||
/* And wake up the poll() call. */
|
||||
write (g_winch_pipe[1], "x", 1);
|
||||
}
|
||||
|
||||
static void
|
||||
install_winch_handler (void)
|
||||
{
|
||||
struct sigaction act, oldact;
|
||||
|
||||
act.sa_handler = winch_handler;
|
||||
act.sa_flags = SA_RESTART;
|
||||
sigemptyset (&act.sa_mask);
|
||||
sigaction (SIGWINCH, &act, &oldact);
|
||||
|
||||
/* Save the ncurses handler. */
|
||||
if (oldact.sa_handler != SIG_DFL
|
||||
&& oldact.sa_handler != SIG_IGN)
|
||||
g_old_winch_handler = oldact.sa_handler;
|
||||
}
|
||||
|
||||
// --- Event handlers ----------------------------------------------------------
|
||||
|
||||
typedef struct
|
||||
{
|
||||
wint_t code;
|
||||
guint is_char : 1;
|
||||
MEVENT mouse;
|
||||
}
|
||||
CursesEvent;
|
||||
|
||||
static gboolean
|
||||
process_curses_event (CursesEvent *event)
|
||||
{
|
||||
if (!event->is_char)
|
||||
{
|
||||
switch (event->code)
|
||||
{
|
||||
case KEY_RESIZE:
|
||||
display ("Screen has been resized to %u x %u\n",
|
||||
COLS, LINES);
|
||||
break;
|
||||
case KEY_MOUSE:
|
||||
display ("Mouse event at (%d, %d), state %#lx\n",
|
||||
event->mouse.x, event->mouse.y, event->mouse.bstate);
|
||||
break;
|
||||
default:
|
||||
display ("Keyboard event: non-character: %u\n",
|
||||
event->code);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
display ("Keyboard event: character: '%s'\n",
|
||||
wchar_to_mb_escaped (event->code));
|
||||
|
||||
if (event->code == L'q' || event->code == KEY_ESCAPE)
|
||||
{
|
||||
display ("Quitting...\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_stdin_input (void)
|
||||
{
|
||||
CursesEvent event;
|
||||
int sta;
|
||||
|
||||
while ((sta = get_wch (&event.code)) != ERR)
|
||||
{
|
||||
event.is_char = (sta == OK);
|
||||
if (sta == KEY_CODE_YES && event.code == KEY_MOUSE
|
||||
&& getmouse (&event.mouse) == ERR)
|
||||
abort ();
|
||||
if (!process_curses_event (&event))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_winch_input (int fd)
|
||||
{
|
||||
char c;
|
||||
|
||||
read (fd, &c, 1);
|
||||
return process_stdin_input ();
|
||||
}
|
||||
|
||||
// --- Main --------------------------------------------------------------------
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
static GOptionEntry entries[] =
|
||||
{
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
if (!setlocale (LC_ALL, ""))
|
||||
abort ();
|
||||
|
||||
GError *error = NULL;
|
||||
GOptionContext *ctx = g_option_context_new ("- StarDict console UI");
|
||||
g_option_context_add_main_entries (ctx, entries, NULL);
|
||||
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||
{
|
||||
g_print ("option parsing failed: %s\n", error->message);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!initscr ()
|
||||
|| cbreak () == ERR
|
||||
|| noecho () == ERR)
|
||||
abort ();
|
||||
|
||||
keypad (stdscr, TRUE); /* Enable character processing. */
|
||||
nodelay (stdscr, TRUE); /* Don't block on get_wch(). */
|
||||
|
||||
mousemask (ALL_MOUSE_EVENTS, NULL);
|
||||
|
||||
display ("Press Q, Escape or ^C to quit\n");
|
||||
|
||||
if (pipe (g_winch_pipe) == -1)
|
||||
abort ();
|
||||
|
||||
install_winch_handler ();
|
||||
|
||||
// --- Message loop ------------------------------------------------------------
|
||||
|
||||
struct pollfd pollfd[2];
|
||||
|
||||
pollfd[0].fd = fileno (stdin);
|
||||
pollfd[0].events = POLLIN;
|
||||
pollfd[1].fd = g_winch_pipe[0];
|
||||
pollfd[1].events = POLLIN;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
if (poll_restart (pollfd, 3, -1) == -1)
|
||||
abort ();
|
||||
|
||||
if ((pollfd[0].revents & POLLIN)
|
||||
&& !process_stdin_input ())
|
||||
break;
|
||||
if ((pollfd[1].revents & POLLIN)
|
||||
&& !process_winch_input (pollfd[2].fd))
|
||||
break;
|
||||
}
|
||||
|
||||
// --- Cleanup -----------------------------------------------------------------
|
||||
|
||||
endwin ();
|
||||
|
||||
if (close (g_winch_pipe[0]) == -1
|
||||
|| close (g_winch_pipe[1]) == -1)
|
||||
abort ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
1070
src/stardict.c
Normal file
1070
src/stardict.c
Normal file
File diff suppressed because it is too large
Load Diff
215
src/stardict.h
Normal file
215
src/stardict.h
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* stardict.h: StarDict API
|
||||
*
|
||||
* This module doesn't cover all the functionality available to StarDict
|
||||
* dictionaries, it should however be good enough for most of them that are
|
||||
* freely available on the Internet.
|
||||
*
|
||||
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STARDICT_H
|
||||
#define STARDICT_H
|
||||
|
||||
/** An object intended for interacting with a dictionary. */
|
||||
typedef struct stardict_dict StardictDict;
|
||||
typedef struct stardict_dict_class StardictDictClass;
|
||||
|
||||
/** Overall information about a particular dictionary. */
|
||||
typedef struct stardict_info StardictInfo;
|
||||
|
||||
/** Handles the task of moving around the dictionary. */
|
||||
typedef struct stardict_iterator StardictIterator;
|
||||
typedef struct stardict_iterator_class StardictIteratorClass;
|
||||
|
||||
/** Contains the decoded data for a single word definition. */
|
||||
typedef struct stardict_entry StardictEntry;
|
||||
typedef struct stardict_entry_class StardictEntryClass;
|
||||
|
||||
/** A single field of a word definition. */
|
||||
typedef struct stardict_entry_field StardictEntryField;
|
||||
|
||||
/* GObject boilerplate. */
|
||||
#define STARDICT_TYPE_DICT (stardict_dict_get_type ())
|
||||
#define STARDICT_DICT(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
|
||||
STARDICT_TYPE_DICT, StardictDict))
|
||||
#define STARDICT_IS_DICT(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
|
||||
STARDICT_TYPE_DICT))
|
||||
#define STARDICT_DICT_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST ((klass), \
|
||||
STARDICT_TYPE_DICT, StardictDictClass))
|
||||
#define STARDICT_IS_DICT_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
|
||||
STARDICT_TYPE_DICT))
|
||||
#define STARDICT_DICT_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
|
||||
STARDICT_TYPE_DICT, StardictDictClass))
|
||||
|
||||
#define STARDICT_TYPE_ITERATOR (stardict_iterator_get_type ())
|
||||
#define STARDICT_ITERATOR(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
|
||||
STARDICT_TYPE_ITERATOR, StardictIterator))
|
||||
#define STARDICT_IS_ITERATOR(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
|
||||
STARDICT_TYPE_ITERATOR))
|
||||
#define STARDICT_ITERATOR_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST ((klass), \
|
||||
STARDICT_TYPE_ITERATOR, StardictIteratorClass))
|
||||
#define STARDICT_IS_ITERATOR_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
|
||||
STARDICT_TYPE_ITERATOR))
|
||||
#define STARDICT_ITERATOR_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
|
||||
STARDICT_TYPE_ITERATOR, StardictIteratorClass))
|
||||
|
||||
#define STARDICT_TYPE_ENTRY (stardict_entry_get_type ())
|
||||
#define STARDICT_ENTRY(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
|
||||
STARDICT_TYPE_ENTRY, StardictEntry))
|
||||
#define STARDICT_IS_ENTRY(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
|
||||
STARDICT_TYPE_ENTRY))
|
||||
#define STARDICT_ENTRY_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST ((klass), \
|
||||
STARDICT_TYPE_ENTRY, StardictEntryClass))
|
||||
#define STARDICT_IS_ENTRY_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
|
||||
STARDICT_TYPE_ENTRY))
|
||||
#define STARDICT_ENTRY_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
|
||||
STARDICT_TYPE_ENTRY, StardictEntryClass))
|
||||
|
||||
// --- Errors ------------------------------------------------------------------
|
||||
|
||||
/** General error type. */
|
||||
typedef enum {
|
||||
STARDICT_ERROR_FILE_NOT_FOUND, //!< Some file was not found
|
||||
STARDICT_ERROR_INVALID_DATA //!< Dictionary contains invalid data
|
||||
} StardictError;
|
||||
|
||||
#define STARDICT_ERROR (stardict_error_quark ())
|
||||
|
||||
GQuark stardict_error_quark (void);
|
||||
|
||||
// --- Dictionary information --------------------------------------------------
|
||||
|
||||
const gchar *stardict_info_get_path (StardictInfo *sdi) G_GNUC_PURE;
|
||||
const gchar *stardict_info_get_book_name (StardictInfo *sdi) G_GNUC_PURE;
|
||||
gsize stardict_info_get_word_count (StardictInfo *sd) G_GNUC_PURE;
|
||||
void stardict_info_free (StardictInfo *sdi);
|
||||
|
||||
GList *stardict_list_dictionaries (const gchar *path);
|
||||
|
||||
// --- Dictionaries ------------------------------------------------------------
|
||||
|
||||
struct stardict_dict
|
||||
{
|
||||
GObject parent_instance;
|
||||
StardictInfo * info; //!< General information about the dict
|
||||
GArray * index; //!< Word index
|
||||
GArray * synonyms; //!< Synonyms
|
||||
gpointer dict; //!< Dictionary data
|
||||
gsize dict_length; //!< Length of the dict data in bytes
|
||||
GMappedFile * mapped_dict; //!< Memory map handle
|
||||
};
|
||||
|
||||
struct stardict_dict_class
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
GType stardict_dict_get_type (void);
|
||||
StardictDict *stardict_dict_new (const gchar *filename, GError **error);
|
||||
StardictDict *stardict_dict_new_from_info (StardictInfo *sdi, GError **error);
|
||||
StardictInfo *stardict_dict_get_info (StardictDict *sd);
|
||||
gchar **stardict_dict_get_synonyms (StardictDict *sd, const gchar *word);
|
||||
StardictIterator *stardict_dict_search
|
||||
(StardictDict *sd, const gchar *word, gboolean *success);
|
||||
|
||||
// --- Dictionary iterators ----------------------------------------------------
|
||||
|
||||
struct stardict_iterator
|
||||
{
|
||||
GObject parent_instance;
|
||||
StardictDict * owner; //!< The related dictionary
|
||||
gint64 offset; //!< Index within the dictionary
|
||||
};
|
||||
|
||||
struct stardict_iterator_class
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
GType stardict_iterator_get_type (void);
|
||||
StardictIterator *stardict_iterator_new (StardictDict *sd, guint32 index);
|
||||
const gchar *stardict_iterator_get_word (StardictIterator *sdi) G_GNUC_PURE;
|
||||
StardictEntry *stardict_iterator_get_entry (StardictIterator *sdi);
|
||||
gboolean stardict_iterator_is_valid (StardictIterator *sdi) G_GNUC_PURE;
|
||||
gint64 stardict_iterator_get_offset (StardictIterator *sdi) G_GNUC_PURE;
|
||||
void stardict_iterator_set_offset
|
||||
(StardictIterator *sdi, gint64 offset, gboolean relative);
|
||||
|
||||
/** Go to the next entry. */
|
||||
#define stardict_iterator_next(sdi) \
|
||||
(stardict_iterator_set_offset (sdi, 1, TRUE))
|
||||
|
||||
/** Go to the previous entry. */
|
||||
#define stardict_iterator_prev(sdi) \
|
||||
(stardict_iterator_set_offset (sdi, -1, TRUE))
|
||||
|
||||
// --- Dictionary entries ------------------------------------------------------
|
||||
|
||||
typedef enum {
|
||||
STARDICT_FIELD_MEANING = 'm', //!< Word's purely textual meaning
|
||||
STARDICT_FIELD_LOCALE = 'l', //!< Locale-dependent meaning
|
||||
STARDICT_FIELD_PANGO = 'g', //!< Pango text markup language
|
||||
STARDICT_FIELD_PHONETIC = 't', //!< English phonetic string
|
||||
STARDICT_FIELD_XDXF = 'x', //!< xdxf language
|
||||
STARDICT_FIELD_YB_KANA = 'y', //!< Chinese YinBiao or Japanese KANA
|
||||
STARDICT_FIELD_POWERWORD = 'k', //!< KingSoft PowerWord's data
|
||||
STARDICT_FIELD_MEDIAWIKI = 'w', //!< MediaWiki markup language
|
||||
STARDICT_FIELD_HTML = 'h', //!< HTML codes
|
||||
STARDICT_FIELD_RESOURCE = 'r', //!< Resource file list
|
||||
STARDICT_FIELD_WAV = 'W', //!< WAV file
|
||||
STARDICT_FIELD_PICTURE = 'P', //!< Picture file
|
||||
STARDICT_FIELD_X = 'X' //!< Reserved, experimental extensions
|
||||
} StardictEntryFieldType;
|
||||
|
||||
struct stardict_entry_field
|
||||
{
|
||||
gchar type; //!< Type of entry (EntryFieldType)
|
||||
gpointer data; //!< Raw data or null-terminated string
|
||||
gsize data_size; //!< Size of data, includding any \0
|
||||
};
|
||||
|
||||
struct stardict_entry
|
||||
{
|
||||
GObject parent_instance;
|
||||
GList * fields; //!< List of StardictEntryField's
|
||||
};
|
||||
|
||||
struct stardict_entry_class
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
GType stardict_entry_get_type (void);
|
||||
const GList *stardict_entry_get_fields (StardictEntry *sde) G_GNUC_PURE;
|
||||
|
||||
#endif /* ! STARDICT_H */
|
||||
429
src/test-stardict.c
Normal file
429
src/test-stardict.c
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* stardict.c: StarDict API test
|
||||
*
|
||||
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "stardict.h"
|
||||
|
||||
|
||||
// --- Utilities ---------------------------------------------------------------
|
||||
|
||||
// Adapted http://gezeiten.org/post/2009/04/Writing-Your-Own-GIO-Jobs
|
||||
static gboolean remove_recursive (GFile *file, GError **error);
|
||||
|
||||
static gboolean
|
||||
remove_directory_contents (GFile *file, GError **error)
|
||||
{
|
||||
GFileEnumerator *enumerator =
|
||||
g_file_enumerate_children (file, "standard::*",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);
|
||||
|
||||
if (!enumerator)
|
||||
return FALSE;
|
||||
|
||||
gboolean success = TRUE;
|
||||
do
|
||||
{
|
||||
GError *err = NULL;
|
||||
GFileInfo *child_info =
|
||||
g_file_enumerator_next_file (enumerator, NULL, &err);
|
||||
|
||||
if (!child_info)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
g_propagate_error (error, err);
|
||||
success = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
GFile *child = g_file_resolve_relative_path
|
||||
(file, g_file_info_get_name (child_info));
|
||||
success = remove_recursive (child, error);
|
||||
g_object_unref (child);
|
||||
g_object_unref (child_info);
|
||||
}
|
||||
while (success);
|
||||
|
||||
g_object_unref (enumerator);
|
||||
return success;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
remove_recursive (GFile *file, GError **error)
|
||||
{
|
||||
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
||||
|
||||
GFileInfo *info = g_file_query_info (file, "standard::*",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error);
|
||||
|
||||
if (!info)
|
||||
return FALSE;
|
||||
|
||||
GFileType type = g_file_info_get_file_type (info);
|
||||
g_object_unref (info);
|
||||
|
||||
if (type == G_FILE_TYPE_DIRECTORY &&
|
||||
!remove_directory_contents (file, error))
|
||||
return FALSE;
|
||||
|
||||
return g_file_delete (file, NULL, error);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
generate_random_string (gsize length, GRand *rand)
|
||||
{
|
||||
GString *s = g_string_sized_new (length);
|
||||
while (length--)
|
||||
g_string_append_c (s, g_rand_int_range (rand, 'a', 'z' + 1));
|
||||
return g_string_free (s, FALSE);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
generate_random_data (gsize length, GRand *rand)
|
||||
{
|
||||
gchar *blob = g_malloc (length), *i = blob;
|
||||
while (length--)
|
||||
*i++ = g_rand_int_range (rand, 0, 256);
|
||||
return blob;
|
||||
}
|
||||
|
||||
// --- Dictionary generation ---------------------------------------------------
|
||||
|
||||
typedef struct dictionary Dictionary;
|
||||
typedef struct test_entry TestEntry;
|
||||
|
||||
struct dictionary
|
||||
{
|
||||
GFile *tmp_dir; //!< A temporary dictionary
|
||||
GFile *ifo_file; //!< The dictionary's .ifo file
|
||||
GArray *data; //!< Array of TestEntry's
|
||||
};
|
||||
|
||||
struct test_entry
|
||||
{
|
||||
gchar *word;
|
||||
gchar *meaning;
|
||||
gpointer data;
|
||||
gsize data_size;
|
||||
};
|
||||
|
||||
static void
|
||||
test_entry_free (TestEntry *te)
|
||||
{
|
||||
g_free (te->word);
|
||||
g_free (te->meaning);
|
||||
g_free (te->data);
|
||||
}
|
||||
|
||||
static gint
|
||||
test_entry_word_compare (gconstpointer a, gconstpointer b)
|
||||
{
|
||||
return strcmp (((TestEntry *) a)->word, ((TestEntry *) b)->word);
|
||||
}
|
||||
|
||||
static GArray *
|
||||
generate_dictionary_data (gsize length)
|
||||
{
|
||||
GRand *rand = g_rand_new_with_seed (0);
|
||||
|
||||
GArray *a = g_array_sized_new (FALSE, FALSE, sizeof (TestEntry), length);
|
||||
g_array_set_clear_func (a, (GDestroyNotify) test_entry_free);
|
||||
|
||||
while (length--)
|
||||
{
|
||||
TestEntry te;
|
||||
|
||||
te.word = generate_random_string
|
||||
(g_rand_int_range (rand, 1, 10), rand);
|
||||
te.meaning = generate_random_string
|
||||
(g_rand_int_range (rand, 1, 1024), rand);
|
||||
|
||||
te.data_size = g_rand_int_range (rand, 0, 1048576);
|
||||
te.data = generate_random_data (te.data_size, rand);
|
||||
|
||||
g_array_append_val (a, te);
|
||||
}
|
||||
|
||||
g_rand_free (rand);
|
||||
g_array_sort (a, test_entry_word_compare);
|
||||
return a;
|
||||
}
|
||||
|
||||
static Dictionary *
|
||||
dictionary_create (void)
|
||||
{
|
||||
GError *error;
|
||||
gchar *tmp_dir_path = g_dir_make_tmp ("stardict-test-XXXXXX", &error);
|
||||
if (!tmp_dir_path)
|
||||
g_error ("Failed to create a directory for the test dictionary: %s",
|
||||
error->message);
|
||||
|
||||
Dictionary *dict = g_malloc (sizeof *dict);
|
||||
dict->tmp_dir = g_file_new_for_path (tmp_dir_path);
|
||||
|
||||
static const gint dictionary_size = 8;
|
||||
dict->data = generate_dictionary_data (dictionary_size);
|
||||
GFile *dict_file = g_file_get_child (dict->tmp_dir, "test.dict");
|
||||
GFile *idx_file = g_file_get_child (dict->tmp_dir, "test.idx");
|
||||
|
||||
GFileOutputStream *dict_stream = g_file_replace (dict_file,
|
||||
NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
|
||||
if (!dict_stream)
|
||||
g_error ("Failed to create the .dict file: %s", error->message);
|
||||
|
||||
GFileOutputStream *idx_stream = g_file_replace (idx_file,
|
||||
NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
|
||||
if (!idx_stream)
|
||||
g_error ("Failed to create the .idx file: %s", error->message);
|
||||
|
||||
GDataOutputStream *dict_data
|
||||
= g_data_output_stream_new (G_OUTPUT_STREAM (dict_stream));
|
||||
g_data_output_stream_set_byte_order
|
||||
(dict_data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
|
||||
|
||||
GDataOutputStream *idx_data
|
||||
= g_data_output_stream_new (G_OUTPUT_STREAM (idx_stream));
|
||||
g_data_output_stream_set_byte_order
|
||||
(idx_data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
|
||||
|
||||
gint i;
|
||||
gsize written;
|
||||
for (i = 0; i < dictionary_size; i++)
|
||||
{
|
||||
TestEntry *te = &g_array_index (dict->data, TestEntry, i);
|
||||
goffset offset = g_seekable_tell (G_SEEKABLE (dict_stream));
|
||||
|
||||
if (!g_data_output_stream_put_string (dict_data,
|
||||
te->meaning, NULL, &error)
|
||||
|| !g_data_output_stream_put_byte (dict_data, '\0', NULL, &error)
|
||||
|| !g_output_stream_write_all (G_OUTPUT_STREAM (dict_stream),
|
||||
te->data, te->data_size, &written, NULL, &error))
|
||||
g_error ("Write to dictionary failed: %s", error->message);
|
||||
|
||||
if (!g_data_output_stream_put_string (idx_data,
|
||||
te->word, NULL, &error)
|
||||
|| !g_data_output_stream_put_byte (idx_data, '\0', NULL, &error)
|
||||
|| !g_data_output_stream_put_uint32 (idx_data, offset, NULL, &error)
|
||||
|| !g_data_output_stream_put_uint32 (idx_data,
|
||||
g_seekable_tell (G_SEEKABLE (dict_stream)) - offset, NULL, &error))
|
||||
g_error ("Write to index failed: %s", error->message);
|
||||
}
|
||||
|
||||
gint index_size = g_seekable_tell (G_SEEKABLE (idx_stream));
|
||||
|
||||
if (!g_output_stream_close (G_OUTPUT_STREAM (dict_stream), NULL, &error))
|
||||
g_error ("Failed to close the .dict file: %s", error->message);
|
||||
if (!g_output_stream_close (G_OUTPUT_STREAM (idx_stream), NULL, &error))
|
||||
g_error ("Failed to close the .idx file: %s", error->message);
|
||||
|
||||
g_object_unref (dict_data);
|
||||
g_object_unref (idx_data);
|
||||
|
||||
g_object_unref (dict_stream);
|
||||
g_object_unref (idx_stream);
|
||||
|
||||
gchar *ifo_contents = g_strdup_printf
|
||||
("StarDict's dict ifo file\n"
|
||||
"version=3.0.0\n"
|
||||
"bookname=Test Book\n"
|
||||
"wordcount=%d\n"
|
||||
"idxfilesize=%d\n"
|
||||
"idxoffsetbits=32\n"
|
||||
"author=Lyra Heartstrings\n"
|
||||
"email=lyra@equestria.net\n"
|
||||
"website=http://equestria.net\n"
|
||||
"description=Test dictionary\n"
|
||||
"date=21.12.2012\n"
|
||||
"sametypesequence=mX\n",
|
||||
dictionary_size, index_size);
|
||||
|
||||
g_object_unref (dict_file);
|
||||
g_object_unref (idx_file);
|
||||
|
||||
dict->ifo_file = g_file_get_child (dict->tmp_dir, "test.ifo");
|
||||
if (!g_file_replace_contents (dict->ifo_file,
|
||||
ifo_contents, strlen (ifo_contents),
|
||||
NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error))
|
||||
g_error ("Failed to create the .ifo file: %s", error->message);
|
||||
g_free (ifo_contents);
|
||||
|
||||
g_message ("Successfully created a test dictionary in %s", tmp_dir_path);
|
||||
g_free (tmp_dir_path);
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
static void
|
||||
dictionary_destroy (Dictionary *dict)
|
||||
{
|
||||
GError *error;
|
||||
if (!remove_recursive (dict->tmp_dir, &error))
|
||||
g_error ("Failed to delete the temporary directory: %s",
|
||||
error->message);
|
||||
|
||||
g_message ("The test dictionary has been deleted");
|
||||
|
||||
g_object_unref (dict->tmp_dir);
|
||||
g_object_unref (dict->ifo_file);
|
||||
g_array_free (dict->data, TRUE);
|
||||
g_free (dict);
|
||||
}
|
||||
|
||||
// --- Testing -----------------------------------------------------------------
|
||||
|
||||
typedef struct dict_fixture DictFixture;
|
||||
|
||||
struct dict_fixture
|
||||
{
|
||||
StardictDict *dict;
|
||||
};
|
||||
|
||||
static void
|
||||
dict_setup (DictFixture *fixture, gconstpointer test_data)
|
||||
{
|
||||
Dictionary *dict = (Dictionary *) test_data;
|
||||
|
||||
gchar *ifo_filename = g_file_get_path (dict->ifo_file);
|
||||
fixture->dict = stardict_dict_new (ifo_filename, NULL);
|
||||
g_free (ifo_filename);
|
||||
}
|
||||
|
||||
static void
|
||||
dict_teardown (DictFixture *fixture, G_GNUC_UNUSED gconstpointer test_data)
|
||||
{
|
||||
g_object_unref (fixture->dict);
|
||||
}
|
||||
|
||||
static void
|
||||
dict_test_list (gconstpointer user_data)
|
||||
{
|
||||
Dictionary *dict = (Dictionary *) user_data;
|
||||
|
||||
gchar *tmp_path = g_file_get_path (dict->tmp_dir);
|
||||
GList *dictionaries = stardict_list_dictionaries (tmp_path);
|
||||
g_free (tmp_path);
|
||||
|
||||
g_assert (dictionaries != NULL);
|
||||
g_assert (dictionaries->next == NULL);
|
||||
|
||||
StardictInfo *info = dictionaries->data;
|
||||
GFile *ifo_file = g_file_new_for_path (stardict_info_get_path (info));
|
||||
g_assert (g_file_equal (ifo_file, dict->ifo_file) == TRUE);
|
||||
g_object_unref (ifo_file);
|
||||
|
||||
g_list_free_full (dictionaries, (GDestroyNotify) stardict_info_free);
|
||||
}
|
||||
|
||||
static void
|
||||
dict_test_new (gconstpointer user_data)
|
||||
{
|
||||
Dictionary *dict = (Dictionary *) user_data;
|
||||
|
||||
gchar *ifo_filename = g_file_get_path (dict->ifo_file);
|
||||
StardictDict *sd = stardict_dict_new (ifo_filename, NULL);
|
||||
g_free (ifo_filename);
|
||||
|
||||
g_assert (sd != NULL);
|
||||
g_object_unref (sd);
|
||||
}
|
||||
|
||||
static void
|
||||
dict_test_data_entry (StardictDict *sd, TestEntry *entry)
|
||||
{
|
||||
gboolean success;
|
||||
StardictIterator *sdi =
|
||||
stardict_dict_search (sd, entry->word, &success);
|
||||
|
||||
g_assert (success == TRUE);
|
||||
g_assert (sdi != NULL);
|
||||
g_assert (stardict_iterator_is_valid (sdi));
|
||||
|
||||
const gchar *word = stardict_iterator_get_word (sdi);
|
||||
g_assert_cmpstr (word, ==, entry->word);
|
||||
|
||||
StardictEntry *sde = stardict_iterator_get_entry (sdi);
|
||||
g_assert (sde != NULL);
|
||||
|
||||
const GList *fields = stardict_entry_get_fields (sde);
|
||||
const StardictEntryField *sdef;
|
||||
g_assert (fields != NULL);
|
||||
g_assert (fields->data != NULL);
|
||||
|
||||
sdef = fields->data;
|
||||
g_assert (sdef->type == 'm');
|
||||
g_assert_cmpstr (sdef->data, ==, entry->meaning);
|
||||
|
||||
fields = fields->next;
|
||||
g_assert (fields != NULL);
|
||||
g_assert (fields->data != NULL);
|
||||
|
||||
sdef = fields->data;
|
||||
g_assert (sdef->type == 'X');
|
||||
g_assert_cmpuint (sdef->data_size, ==, entry->data_size);
|
||||
g_assert (memcmp (sdef->data, entry->data, entry->data_size) == 0);
|
||||
|
||||
fields = fields->next;
|
||||
g_assert (fields == NULL);
|
||||
|
||||
g_object_unref (sde);
|
||||
g_object_unref (sdi);
|
||||
}
|
||||
|
||||
static void
|
||||
dict_test_data (DictFixture *fixture, gconstpointer user_data)
|
||||
{
|
||||
Dictionary *dict = (Dictionary *) user_data;
|
||||
GArray *data = dict->data;
|
||||
StardictDict *sd = fixture->dict;
|
||||
|
||||
guint i;
|
||||
for (i = 0; i < data->len; i++)
|
||||
{
|
||||
TestEntry *entry = &g_array_index (data, TestEntry, i);
|
||||
dict_test_data_entry (sd, entry);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
if (glib_check_version (2, 36, 0))
|
||||
g_type_init ();
|
||||
|
||||
Dictionary *dict = dictionary_create ();
|
||||
|
||||
g_test_add_data_func ("/dict/list", dict, dict_test_list);
|
||||
g_test_add_data_func ("/dict/new", dict, dict_test_new);
|
||||
|
||||
g_test_add ("/dict/data", DictFixture, dict,
|
||||
dict_setup, dict_test_data, dict_teardown);
|
||||
|
||||
int result = g_test_run ();
|
||||
dictionary_destroy (dict);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user