/* This file is part of LingoTeach, the Language Teaching program * Copyright (C) 2001-2003 The LingoTeach Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "lingoteach.h" #include "conf.h" #include "meaning.h" /* some static XPaths, which will usually never change */ #define QUERY_FIND "/%s/meaning[@id='m%i']/translation[@language='%s']/text()" #define QUERY_DESCR "/%s/meaning[@id='m%i']/description[@language='%s']/text()" #define QUERY_SOUND "/%s/lang[@id='%s']/speaker/text()" #define QUERY_TYPE "/%s/meaning[@id='m%i']/@type" /********************* * private functions * *********************/ /* * searches the word, which matches int meaning, in the language, which is * given as char* language */ lingchar* meaning_find_word (int id, lingchar *language, lessonData *lesson) { xmlXPathObjectPtr ptr; lingchar *query; lingchar *retval = ""; /* build the search query */ query = malloc (strlen (QUERY_FIND) + strlen (lesson->settings->appname) + strlen (language) + sizeof (id)); if (query == NULL) return NULL; sprintf (query, QUERY_FIND, lesson->settings->appname, id, language); ptr = xmlXPathEval (query, lesson->x_path); if (ptr == NULL) { free (query); return NULL; } retval = xmlXPathCastToString (ptr); xmlXPathFreeObject (ptr); free (query); return retval; } /* * searches the sound matching the meaningId id and language */ char* meaning_find_sound (char *soundpath, int id, lingchar *language, lingLesson *lesson) { lingchar *query; lingchar *qsound; char *playbase = "%s/%s/%s/%s/%i.ogg"; lingchar *dir; lingchar *speaker; char *soundname; lessonData *data = (lessonData *) lesson->pdata; xmlXPathObjectPtr ptr; xmlDocPtr lang; xmlXPathContextPtr langCtxt; if (data->settings->langfile == NULL) return NULL; lang = xmlParseFile (data->settings->langfile); if (lang == NULL) return NULL; langCtxt = lesson_get_xpath (lang); if (langCtxt == NULL) { xmlFreeDoc (lang); return NULL; } /* the speaker */ query = malloc (strlen (QUERY_SOUND) + strlen (data->settings->appname) + strlen (language)); if (query == NULL) { xmlFreeDoc (lang); xmlXPathFreeContext (langCtxt); return NULL; } sprintf (query, QUERY_SOUND, data->settings->appname, language); ptr = xmlXPathEval (query, langCtxt); if (ptr == NULL) { xmlFreeDoc (lang); xmlXPathFreeContext (langCtxt); free (query); return NULL; } speaker = xmlXPathCastToString (ptr); /* free superfluos things */ xmlFreeDoc (lang); xmlXPathFreeObject (ptr); xmlXPathFreeContext (langCtxt); free (query); /* the sounddir */ qsound = malloc (strlen (data->settings->appname) + 8); if (qsound == NULL) return NULL; sprintf (qsound, "/%s/@sound", data->settings->appname); ptr = xmlXPathEval (qsound, data->x_path); if (ptr == NULL) { free (qsound); return NULL; } dir = xmlXPathCastToString (ptr); soundname = malloc (strlen (playbase) + strlen (language) + strlen (speaker) + strlen (soundpath) + strlen (dir) + sizeof (id)); if (soundname == NULL) { xmlXPathFreeObject (ptr); free (qsound); return NULL; } sprintf (soundname, playbase, soundpath, language, speaker, dir, id); #ifdef DEBUG fprintf (stdout, "Debug: Looking for sound: %s\n", soundname); #endif xmlXPathFreeObject (ptr); free (qsound); return soundname; } /* * returns the type of a meaning */ lingchar* meaning_get_type (int id, lessonData *lesson) { xmlXPathObjectPtr ptr; lingchar *query; lingchar *retval = ""; /* build the search query */ query = malloc (strlen (QUERY_TYPE) + sizeof (id) + strlen (lesson->settings->appname)); if (query == NULL) return NULL; sprintf (query, QUERY_TYPE, lesson->settings->appname, id); ptr = xmlXPathEval (query, lesson->x_path); if (ptr == NULL) { free (query); return NULL; } retval = xmlXPathCastToString (ptr); xmlXPathFreeObject (ptr); free (query); return retval; } /* * returns a possible description of the meaning in the given language */ lingchar* meaning_get_description (int id, lingchar *language, lessonData *lesson) { xmlXPathObjectPtr ptr; lingchar *query; lingchar *retval = ""; /* build the search query */ query = malloc (strlen (QUERY_FIND) + strlen (lesson->settings->appname) + strlen (language) + sizeof (id)); if (query == NULL) return NULL; sprintf (query, QUERY_DESCR, lesson->settings->appname, id, language); ptr = xmlXPathEval (query, lesson->x_path); if (ptr == NULL) { free (query); return NULL; } retval = xmlXPathCastToString (ptr); xmlXPathFreeObject (ptr); free (query); return retval; } /* * internal helper for getting the maximum possible id */ int meaning_get_max_overall (lingLesson *lesson) { int max_id = 0; int prev = 0; while (lesson != NULL) { max_id = ((lessonData *) lesson->pdata)->meanings; if (max_id > prev) prev = max_id; if (lesson->next == NULL) break; lesson = lesson->next; } #ifdef DEBUG fprintf (stdout, "Debug: Maximum meanings %i\n", prev); #endif return prev; } /* * creates a new node tree of meanings */ xmlNodePtr meaning_create_node_tree (lingMeaning *meaning, xmlNodePtr parent) { xmlNodePtr child = NULL; xmlNodePtr trans; xmlNodePtr new = NULL; lingchar *tmp = NULL; int id; while (meaning != NULL) { id = meaning->id; child = parent->children; if (child == NULL) new = xmlNewChild (parent, NULL, "meaning", NULL); else new = xmlNewNode (NULL, "meaning"); /* create the id attribute */ tmp = malloc (sizeof (int) + 1); if (tmp == NULL) return NULL; snprintf (tmp, sizeof (int), "m%i", meaning->id); xmlNewProp (new, "id", tmp); free (tmp); /* create type attribute */ if (meaning->type != NULL) xmlNewProp (new, "type", meaning->type); if (child) xmlAddSibling (child, new); /* add the translations */ while (meaning != NULL && id == meaning->id) { trans = xmlNewTextChild (new, NULL, "translation", meaning->translation); xmlNewProp (trans, "language", meaning->language); meaning = meaning->next; } } return parent; } /******************** * public functions * ********************/ /** * Creates a new lingMeaning and returns it. * The lingMeaning has to be freed by the user. * * \return A new, empty lingMeaning. */ lingMeaning* ling_meaning_get_new (void) { lingMeaning *mn = malloc (sizeof (lingMeaning)); if (mn == NULL) return NULL; mn->translation = NULL; mn->description = NULL; mn->lesson = NULL; mn->language = NULL; mn->type = NULL; mn->id = 0; mn->next = NULL; mn->prev = NULL; return mn; } /** * Gets another lingMeaning from a lesson file. * The lingMeaning has to be freed by the user. * * \param lesson The lesson to fetch the meaning from. * \param type The method to use for getting the lingMeaning. * \param language The language, which should be used. * \return A meaning pointer, using the method given as argument * to the function. */ lingMeaning* ling_meaning_get_another_meaning (lingLesson *lesson, Method type, char *language) { int i; /* int max = meaning_get_max_overall (lesson); */ int max = ((lessonData *) lesson->pdata)->meanings; #ifdef DEBUG fprintf (stdout, "Maximum meanings %i\n", max); #endif i = 1 + (int) (1.0 * max * rand () / (RAND_MAX + 1.0)); switch (type) { case RANDOM: return ling_meaning_get_by_word_id (lesson, i, language); case REVIEW: return ling_meaning_get_by_word_id (lesson, i, language); case LEARN: return ling_meaning_get_by_word_id (lesson, i, language); default: return ling_meaning_get_by_word_id (lesson, i, language); } } /** * Gets a specific lingMeaning from the given lesson. * The lingMeaning has to be freed by the user. * * \param lesson The lesson to fetch the meaning from. * \param id The id, which should be searched for. * \param language The language, which should be used. * \return a lingMeaning containing the meaning, which has the given id. * If none is found with the given language, the function returns NULL. */ lingMeaning* ling_meaning_get_by_word_id (lingLesson *lesson, int id, char *language) { lingMeaning *current = malloc (sizeof (lingMeaning)); if (current == NULL) return NULL; current->translation = meaning_find_word (id, language, (lessonData *) lesson->pdata); if (current->translation == NULL) { free (current); return NULL; } current->language = ling_malloc (strlen (language)); if (current->language == NULL) { ling_free (current->translation); free (current); return NULL; } strcpy (current->language, language); current->description = meaning_get_description (id, language, (lessonData *) lesson->pdata); current->type = meaning_get_type (id, (lessonData *) lesson->pdata); current->id = id; current->lesson = lesson; current->next = NULL; current->prev = NULL; return current; } /** * Frees the memory used by a list of lingMeaning and the lingMeanings itself * * \param meaning The meaning list to free. */ void ling_meaning_free_meaning (lingMeaning *meaning) { lingMeaning *prev; while (meaning != NULL) { #ifdef DEBUG fprintf (stdout, "Debug: Freeing Meaning...\n"); #endif prev = meaning; meaning = prev->next; if (prev->translation) xmlFree (prev->translation); if (prev->language) xmlFree (prev->language); if (prev->type) xmlFree (prev->type); if (prev->description) xmlFree (prev->description); free (prev); } return; } /** * Frees the memory used by a lingMeaning and the lingMeaning itself * * \param tree The list of meanings, in which the meanings is. * \param meaning The meaning, which should be freed. * return The new list without the freed meaning. */ lingMeaning* ling_meaning_free_meaning_1 (lingMeaning *tree, lingMeaning *node) { lingMeaning *tmp = tree; if (tmp == node) tree = tree->next; else { while (tmp != node) tmp = tmp->next; if (tmp->prev) tmp->prev->next = tmp->next; if (tmp->next) tmp->next->prev = tmp->prev; } tmp->next = NULL; tmp->prev = NULL; if (tmp->translation) xmlFree (tmp->translation); if (tmp->type) xmlFree (tmp->type); if (tmp->language) xmlFree (tmp->language); if (tmp->description) xmlFree (tmp->description); free (tmp); return tree; } /** * Saves a list of meanings into a given lesson file. The format is * the standard lingoteach lesson format. * * \param meaning The list of meanings to save. * \param filename The file the meanings should be saved in. * \param settings The settings to use for the file. * \return TRUE on succesful saving, else FALSE. */ lingbool ling_meaning_save_meanings (lingMeaning *meaning, char *filename, lingConfig *settings) { xmlDocPtr lesson; if (meaning == NULL) return FALSE; if (ling_lesson_create_new (filename, 0) == NULL) return FALSE; lesson = xmlParseFile (filename); if (lesson == NULL) return FALSE; lesson->parent = xmlDocGetRootElement (lesson); if (lesson->parent == NULL || lesson->parent->name == NULL || xmlStrncmp (lesson->parent->name, settings->appname, strlen (lesson->parent->name)) != 0) { #ifdef DEBUG fprintf (stdout, "Debug: Rootnode does not seem to be correct. Check %s\n", filename); #endif xmlFreeDoc (lesson); return FALSE; } /* create the tree */ lesson->parent = meaning_create_node_tree (meaning, lesson->parent); xmlKeepBlanksDefault (0); if (xmlSaveFormatFile (filename, lesson, 1) == -1) /* save */ { xmlFreeDoc (lesson); return FALSE; } xmlFreeDoc (lesson); return TRUE; } /** * Modifies a meaning of the given list of meanings * * \param tree The list of meanings in which the meaning exists. * \param id The id of the meaning, which should be modified. * \param meaning The modified meaning . * \return The tree with the modified meaning. */ lingMeaning* ling_meaning_modify_meaning (lingMeaning *tree, int id, lingMeaning *meaning) { lingMeaning *node; lingMeaning *prev; lingMeaning *next; if (tree) { node = tree; while (node->id != id) { if (!node->next) return NULL; node = node->next; } prev = node->prev; next = node->next; meaning->next = next; meaning->prev = prev; ling_meaning_free_meaning_1 (tree, node); next->prev = meaning; prev->next = meaning; } return tree; } /** * Adds a new meaning at the end of the given list. * * \param tree The meaning list to which the meaning should be added. * \param meaning The meaning to add to the tree. * \return The new, modified tree. */ lingMeaning* ling_meaning_add_meaning (lingMeaning *tree, lingMeaning *meaning) { lingMeaning *tmp = tree; while (tmp->next != NULL) tmp = tmp->next; tmp->next = meaning; meaning->prev = tmp; return tree; } /** * Inserts a meaning after specific meaning into a meaning list. * * \param tree The meaning list to which the meaning should be added. * \param parent The parent meaning, after whihc the child should be added. * \param child The meaning to add. * \return The new, modified tree. */ lingMeaning* ling_meaning_insert_after_meaning (lingMeaning *tree, lingMeaning *parent, lingMeaning *child) { lingMeaning *prev = tree; lingMeaning *next = NULL; if (parent != NULL) { while (prev != parent) prev = prev->next; if (prev->next != NULL) next = prev->next; prev = prev->prev; if (child != NULL) { prev->next = child; child->prev = prev; if (next != NULL) { next->prev = child; child->next = next; } } } return tree; } /** * Returns the path to the sound snippet for the given meaning. * * \param soundpath The full qualified path to the sound files. * \param meaning The lingMeaning the sound snippet has to be found for. * \return The full qualified path to the sound snippet of the meaning. */ char* ling_meaning_find_sound (char *soundpath, lingMeaning *meaning) { return (meaning_find_sound (soundpath, meaning->id, meaning->language, meaning->lesson)); } /** * Allocates a chunk of memory for usage. * * \param bytes The count of bytes to allocate * \return A pointer to the newly allocated space */ void* ling_malloc (size_t bytes) { return xmlMalloc (bytes); } /** * Frees the memory hold by a pointer, which was previously allocated * using ling_malloc(). * * \param ptr The pointer to free. */ void ling_free (void *ptr) { xmlFree (ptr); return; }