/* 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" #include "lesson.h" /* some static XPaths, which will usually never change */ #define SEARCH_ID "/%s/meaning[last()]/@id" /********************* * private functions * *********************/ /* * allocates memory for a new lesson */ lingLesson* lesson_alloc_new (void) { lingLesson *new = malloc (sizeof (lingLesson)); if (new == NULL) return NULL; new->pdata = NULL; new->type = NULL; new->next = NULL; return new; } /* * returns a xmlXPathContextPtr of the given xmlDocPtr */ xmlXPathContextPtr lesson_get_xpath (xmlDocPtr doc) { xmlXPathInit (); return xmlXPathNewContext (doc); } /* * returns the last lesson from the list of used ones */ lingLesson* lesson_get_last (lingLesson *lesson) { while (lesson->next != NULL) lesson = lesson->next; return lesson; } /* * returns the maximum word id, not the 'real' count of sets. * if there are missing numbers within the series, this function * only returns the number of the last set. Do not use it for * counting the sets. It will not work. */ int lesson_get_max_word_id (lessonData *data) { xmlXPathContextPtr lessonCtxt = data->x_path; xmlXPathObjectPtr ptr; lingchar *tmp; lingchar *search; int i; /* build the search path */ search = malloc (strlen (SEARCH_ID) + strlen (data->settings->appname)); if (search == NULL) return -1; sprintf (search, SEARCH_ID, data->settings->appname); #ifdef DEBUG fprintf (stdout, "Debug: query is %s\n", search); #endif /* create the XPath object */ if ((ptr = xmlXPathEval (search, lessonCtxt)) == NULL) { xmlXPathFreeObject (ptr); free (search); return -1; } free (search); tmp = xmlXPathCastToString (ptr); if (tmp == NULL) { xmlXPathFreeObject (ptr); return -1; } if (xmlStrncmp (tmp, "", strlen (tmp)) == 0) { xmlXPathFreeObject (ptr); /* xmlFree (tmp); */ return -1; } xmlXPathFreeObject (ptr); tmp = strtok (tmp, "m"); i = abs (atoi (tmp)); /* xmlFree (tmp); */ /* optional on demand, see xmlXPathCastToString() api */ #ifdef DEBUG fprintf (stdout, "Debug: Maximum of meanings: %i\n", i); #endif return i; } /* * creates and returns the lesson data for a file */ void* lesson_create_lesson_data (char *filename, lingConfig *settings) { lessonData *new = malloc (sizeof (lessonData)); if (new == NULL) return NULL; /* settings */ new->settings = malloc (sizeof (settings)); if (new->settings != NULL) { new->settings->appname = malloc (strlen (settings->appname) + 1); if (new->settings->appname == NULL) { free (new->settings); free (new); return NULL; } new->settings->langfile = malloc (strlen (settings->langfile) + 1); if (new->settings->langfile == NULL) { free (new->settings->appname); free (new->settings); free (new); return NULL; } /* copy the settings */ strncpy (new->settings->appname, settings->appname, strlen (settings->appname) + 1); strncpy (new->settings->langfile, settings->langfile, strlen (settings->langfile) + 1); } else { free (new); return NULL; } new->lesson = xmlParseFile (filename); if (new->lesson == NULL) /* the lesson */ { free (new->settings->appname); free (new->settings->langfile); free (new->settings); free (new); return NULL; } /* create the XmlXPath stuff */ xmlXPathOrderDocElems (new->lesson); /* speed up searches */ new->x_path = lesson_get_xpath (new->lesson); if (new->x_path == NULL) { xmlFreeDoc (new->lesson); free (new->settings->appname); free (new->settings->langfile); free (new->settings); free (new); return NULL; } new->meanings = lesson_get_max_word_id (new); /* count meanings */ new->used = TRUE; /* assumed default */ /* copy filepath */ new->path = malloc (strlen (filename) + 1); if (new->path == NULL) { xmlFreeDoc (new->lesson); xmlXPathFreeContext (new->x_path); free (new->settings->appname); free (new->settings->langfile); free (new->settings); free (new); return NULL; } strncpy (new->path, filename, strlen (filename) + 1); return new; } /* * frees all memory hold by the passed lessonData, including the lessonData * itself */ void lesson_free_lesson_data (lessonData *data) { xmlFreeDoc (data->lesson); xmlXPathFreeContext (data->x_path); free (data->settings->appname); free (data->settings->langfile); free (data->settings); free (data->path); free (data); return; } /* * saves the passed lesson into the passed file */ lingbool lesson_save_lesson (lingLesson *lesson, char *filename) { xmlDocPtr doc = ((lessonData *) lesson->pdata)->lesson; xmlKeepBlanksDefault(0); #ifdef WITH_COMP xmlSetDocCompressMode (doc, 3); #endif if (xmlSaveFormatFile (filename, doc, 1) == -1) /* save */ return FALSE; return TRUE; } /* * gets a specific description of a meaning node */ lingchar* lesson_node_get_description (xmlNodePtr node, lingchar *language) { xmlNodePtr descr = NULL; lingchar *tmp; for (descr = node->children; descr != NULL; descr = descr->next) { if (xmlIsBlankNode (descr) == 1 || xmlStrncmp (descr->name, "description", strlen (descr->name) != 0)) continue; tmp = xmlGetProp (descr, "language"); if (xmlStrncmp (tmp, language, strlen (tmp)) == 0) { xmlFree (tmp); return xmlNodeGetContent (descr); } } return NULL; } /******************** * public functions * ********************/ /** * Prepares and adds a lesson file to an existing list of lessons. * * \param lesson The lesson list, the new lesson should be added to. * If lesson is NULL, a new list will be created and returned. * \param filename The full qualified file path of the file. * \param settings The settings to use for the lesson. * \return The new lesson list. */ lingLesson* ling_lesson_add_lesson (lingLesson *lesson, char *filename, lingConfig *settings) { lingLesson *new; lingLesson *tmp; lessonData *data; if (filename == NULL) return NULL; new = lesson_alloc_new (); if (new == NULL) /* allocate new lesson node */ return NULL; /* create lessonData stuff */ new->pdata = lesson_create_lesson_data (filename, settings); if (new->pdata == NULL) { #ifdef DEBUG fprintf (stdout, "Debug: lesson data structure could not be created!"); #endif free (new); return NULL; } new->next = NULL; /* determine the type */ data = (lessonData *) new->pdata; data->lesson->parent = xmlDocGetRootElement (data->lesson); new->type = xmlGetProp (data->lesson->parent, "type"); if (lesson) /* link nodes */ { tmp = lesson_get_last (lesson); tmp->next = new; } else return new; return lesson; } /** * Removes a lesson from the list of used ones and frees all its * internal references. * * \param lesson The lesson list to look for the lesson. * \param node The lesson to free. * \return The modified lesson list. */ lingLesson* ling_lesson_remove_lesson (lingLesson *lesson, lingLesson *node) { lingLesson *tmp; lingLesson *prev = NULL; tmp = lesson; while (tmp) { if (tmp == node) { /* link the previous and next node */ (prev != NULL) ? (prev->next = tmp->next) : (lesson = tmp->next); /* free all memory hold by the lesson */ lesson_free_lesson_data ((lessonData *) tmp->pdata); if (tmp->type) xmlFree (tmp->type); free (tmp); break; } prev = tmp; tmp = prev->next; } return lesson; } /** * Returns the full qualified file path of the matching lesson entry. * * \param lesson The lesson for which the path should be returned. * \return The lesson path of the lesson, else NULL. */ char* ling_lesson_return_path (lingLesson *lesson) { return ((lessonData *) lesson->pdata)->path; } /** * Modifies the usage flag of a lesson, so that it will be automatically used * by the different meaning access functions. * * \param lesson The lesson to use * \param use A boolean statement for usage (TRUE or FALSE). */ void ling_lesson_use_lesson (lingLesson *lesson, lingbool use) { ((lessonData *) lesson->pdata)->used = use; return; } /** * Returns the status of the use-flag of a lesson. * * \param lesson The lesson, the usage flag should be returned for. * \return TRUE, if the lesson is currently marked as used * in the internal list, else FALSE. */ lingbool ling_lesson_return_used (lingLesson *lesson) { return ((lessonData *) lesson->pdata)->used; } /** * Returns the last meaning id of the given lesson. * * \param lesson The lesson, for which the max. meaning id should be returned. * \return the maximum (last!) meaning id of a lesson file (can be 0, if it * fails). */ int ling_lesson_get_max_meaning (lingLesson *lesson) { return ((lessonData *) lesson->pdata)->meanings; } /** * Saves a lesson into the passed file. * If the file does not exist, it will be automatically created, else * its contents will be completely overwritten. * * \param lesson The lesson to save. * \param filename The lesson file for saving the lesson. * \return TRUE, if the lesson could be saved, else FALSE. */ lingbool ling_lesson_save_lesson (lingLesson *lesson, char *filename) { FILE *fp; if (filename == NULL) { filename = ((lessonData *) lesson->pdata)->path; if (filename == NULL) return FALSE; } fp = fopen (filename, "r"); if (fp == NULL) { if (ling_lesson_create_new (filename, 0) == NULL) return FALSE; } else fclose (fp); return lesson_save_lesson (lesson, filename); } /** * Creates a new template lesson with optional empty meanings. * * \param filename The lesson file to create. * \param meanings The amount of meaning templates to create. * \return The filename on success or NULL */ char* ling_lesson_create_new (char *filename, int meanings) { FILE *fp; fp = fopen (filename, "w+"); if (fp != NULL) { /* dump a basic tree into the file */ fprintf (fp, "\n" "\n" "\n" "\n" "\n" "\n"); while (--meanings > 0) fprintf (fp, " \n \n", meanings); fprintf (fp, "\n"); fclose (fp); return filename; } return NULL; } /** * Creates a linked list of meanings, which are available in the given file. * * \param lesson The lesson, from which the tree should be created. * \return A linked list of meanings of the lesson. */ lingMeaning* ling_lesson_create_tree (lingLesson *lesson) { xmlNodePtr child; xmlNodePtr trans; int id; lingchar *tmp; lingMeaning *meaning = NULL; lingMeaning *new = NULL; lingMeaning *prev; lessonData *data = (lessonData *) lesson->pdata; if (!data) return NULL; /* check the parent */ data->lesson->parent = xmlDocGetRootElement (data->lesson); if (data->lesson->parent == NULL || data->lesson->parent->name == NULL) return NULL; /* check for children */ child = data->lesson->parent->children; if (child == NULL) return NULL; /* create tree */ for (child = child->next; child != NULL ; child = child->next) { if (xmlStrncmp (child->name, "meaning", strlen (child->name)) != 0) continue; /* necessary content for all similar meanings */ tmp = xmlGetProp (child, "id"); if (tmp == NULL) /* this should not happen! */ { if (meaning != NULL) ling_meaning_free_meaning (meaning); return NULL; } id = abs (atoi (strtok (tmp, "m"))); xmlFree (tmp); trans = child->children; /* get translations */ for (trans = trans; trans != NULL; trans = trans->next) { if (xmlIsBlankNode (trans) == 1 || xmlStrncmp (trans->name, "translation", strlen (trans->name) != 0)) continue; if (meaning == NULL) { meaning = ling_meaning_get_new (); if (meaning == NULL) return NULL; new = meaning; new->prev = NULL; new->next = NULL; } else { new->next = ling_meaning_get_new (); if (new->next == NULL) { ling_meaning_free_meaning (meaning); return NULL; } prev = new; new = prev->next; new->prev = prev; new->next = NULL; } /* fill meanings and link them */ new->id = id; new->type = xmlGetProp (child, "type"); new->language = xmlGetProp (trans, "language"); new->translation = xmlNodeGetContent (trans); new->description = lesson_node_get_description(child, new->language); } } return meaning; } /** * Returns a random lesson of the lesson list, which is passed as argument. * Lessons which are not used are ignored. The function tries to determine * an used lesson twenty times. If none is found, NULL will be returned. * * \param lesson The lesson list to use for the random lesson. * \return A random lesson or NULL if none found after twenty rounds. */ lingLesson* ling_lesson_return_rand_lesson (lingLesson *lesson) { lingLesson *cnt = lesson; int steps = 20; /* do not let it get into an endless loop */ int i = 1; int j = 1; while (cnt->next) { cnt = cnt->next; j++; } cnt = lesson; do { lesson = cnt; i = (int) (1.0 * j * rand () / (RAND_MAX + 1.0)); #ifdef DEBUG fprintf (stdout, "Debug: random lesson no. %i of %i\n", i, j); #endif while (--i >= 0) lesson = lesson->next; if (--steps < 0) return NULL; } while (((lessonData *)lesson->pdata)->used != TRUE); return lesson; }