diff options
Diffstat (limited to 'app/bin/partcatalog.c')
-rw-r--r-- | app/bin/partcatalog.c | 1371 |
1 files changed, 815 insertions, 556 deletions
diff --git a/app/bin/partcatalog.c b/app/bin/partcatalog.c index a1db09c..205ae50 100644 --- a/app/bin/partcatalog.c +++ b/app/bin/partcatalog.c @@ -1,7 +1,7 @@ /** \file partcatalog.c * Manage the catalog of track parameter files */ -/* XTrkCad - Model Railroad CAD +/* XTrackCAD - Model Railroad CAD * Copyright (C) 2019 Martin Fischer * * This program is free software; you can redistribute it and/or modify @@ -16,44 +16,29 @@ * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <assert.h> -#include <ctype.h> -#ifdef HAVE_MALLOC_H -#include <malloc.h> -#endif -#include <search.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <sys/types.h> -#include <sys/stat.h> - -#ifdef WINDOWS - #include "include/dirent.h" -#else - #include <dirent.h> -#endif +#include "dynstring.h" #include "fileio.h" +#include "include/levenshtein.h" #include "misc.h" #include "include/paramfile.h" #include "include/partcatalog.h" #include "paths.h" #include "include/stringxtc.h" #include "include/utf8convert.h" - -#if _MSC_VER > 1300 - #define strnicmp _strnicmp - #define strdup _strdup -#endif +#include "include/utlist.h" #define PUNCTUATION "+-*/.,&%=#" +#define SEARCHDELIMITER " \t\n\r/" +#define LDISTANCELIMIT (2) +static char *stopwords = { + "scale", +}; +static int log_params; /** * Create and initialize the linked list for the catalog entries @@ -61,24 +46,49 @@ * \return pointer to first element */ -CatalogEntry * +Catalog * InitCatalog(void) { - CatalogEntry *head; - CatalogEntry *tail; - - /* allocate two pseudo nodes for beginning and end of list */ - head = (CatalogEntry *)malloc(sizeof(CatalogEntry)); - tail = (CatalogEntry *)malloc(sizeof(CatalogEntry)); - - head->next = tail; - tail->next = tail; + Catalog *newCatalog = MyMalloc(sizeof(Catalog)); + if (newCatalog) { + newCatalog->head = NULL; + } + return (newCatalog); +} +/** + * Destroys the catalog + * + * \param [in] catalog + */ +void +DestroyCatalog(Catalog *catalog) +{ + CatalogEntry *current = catalog->head; + CatalogEntry *entry = NULL; + CatalogEntry *tmp = NULL; +// CatalogEntry *old = NULL; + DL_FOREACH_SAFE(current, entry, tmp) { + //if (old) MyFree(old); +// old = NULL; + for (unsigned int i = 0; i < entry->files; i++) { + MyFree(entry->fullFileName[i]); + entry->fullFileName[i] = NULL; + } + entry->files = 0; + MyFree(entry->contents); + entry->contents = NULL; + MyFree(entry->tag); + entry->tag = NULL; +// old = entry; + DL_DELETE(catalog->head,entry); + } - return (head); + catalog->head = NULL; } +#if 0 /** * Create a new CatalogEntry and add it to the linked list. The newly * created entry is inserted into the list after the given position @@ -90,33 +100,32 @@ InitCatalog(void) static CatalogEntry * InsertIntoCatalogAfter(CatalogEntry *entry) { - CatalogEntry *newEntry = (CatalogEntry *)malloc(sizeof(CatalogEntry)); - newEntry->next = entry->next; - entry->next = newEntry; - newEntry->files = 0; - newEntry->contents = NULL; - - return (newEntry); + CatalogEntry *newEntry = (CatalogEntry *)MyMalloc(sizeof(CatalogEntry)); + newEntry->next = entry->next; + newEntry->prev = entry; + entry->next = newEntry; + newEntry->files = 0; + newEntry->contents = NULL; + newEntry->tag = NULL; + + return (newEntry); } +#endif /** - * Count the elements in the linked list ignoring dummy elements + * Count the elements in the linked list * - * \param listHeader IN the linked list - * \return the numberof elements + * \param catalog IN + * \return the number of elements */ unsigned -CountCatalogEntries(CatalogEntry *listHeader) +CountCatalogEntries(Catalog *catalog) { - CatalogEntry *currentEntry = listHeader->next; - unsigned count = 0; - - while (currentEntry != currentEntry->next) { - count++; - currentEntry = currentEntry->next; - } - return (count); + CatalogEntry * entry; + unsigned count = 0; + DL_COUNT(catalog->head, entry, count); + return (count); } /** @@ -125,119 +134,134 @@ CountCatalogEntries(CatalogEntry *listHeader) * \param listHeader IN the list */ -void -EmptyCatalog(CatalogEntry *listHeader) +EXPORT void +CatalogDiscard(Catalog *catalog) { - CatalogEntry *current = listHeader; - - while (current->next != current->next->next) { - CatalogEntry *removedElement; - removedElement = current->next; - current->next = current->next->next; - if (removedElement->contents) { - free(removedElement->contents); - } - for (unsigned int i = 0; i < removedElement->files; i++) { - free(removedElement->fullFileName[i]); - } - free(removedElement); - } + CatalogEntry *current = catalog->head; + CatalogEntry *element; + CatalogEntry *tmp; +// CatalogEntry *old = NULL; + + DL_FOREACH_SAFE(current, element, tmp) { + //if (old) MyFree(old); +// old = NULL; + MyFree(element->contents); + element->contents = NULL; + MyFree(element->tag); + element->tag = NULL; + for (unsigned int i = 0; i < element->files; i++) { + MyFree(element->fullFileName[i]); + element->fullFileName[i] = NULL; + } + element->files = 0; +// old = element; + DL_DELETE(catalog->head,element); + } + + catalog->head = NULL; } /** - * Find the position in the list and add + * Compare entries * - * \param listHeader IN start of list - * \param contents IN contents to include + * \param [in] a If non-null, a CatalogEntry to compare. + * \param [in] b If non-null, a CatalogEntry to compare. * - * \return CatalogEntry if found, NULL otherwise + * \returns An int. */ -static CatalogEntry * -InsertInOrder(CatalogEntry *listHeader, const char *contents) +static int +CompareEntries(CatalogEntry *a, CatalogEntry *b) { - CatalogEntry *currentEntry = listHeader; - - while (currentEntry->next != currentEntry->next->next) { - CatalogEntry *nextEntry = currentEntry->next; - if (XtcStricmp(nextEntry->contents, contents)>0) { - return InsertIntoCatalogAfter(currentEntry); - } - currentEntry = nextEntry; - } - return InsertIntoCatalogAfter(currentEntry); + return XtcStricmp(a->contents, b->contents); } + /** - * Get the existing list element for a content + * Create a new CatalogEntry and insert it keeping the list sorted * - * \param listHeader IN start of list - * \param contents IN contents to search - * \param Do we log error messages or not + * \param [in] catalog + * \param [in] contents to include. + * \param [in] tag * - * \return CatalogEntry if found, NULL otherwise + * \returns CatalogEntry */ -static CatalogEntry * -IsExistingContents(CatalogEntry *listHeader, const char *contents, BOOL_T silent) +EXPORT CatalogEntry * +InsertInOrder(Catalog *catalog, const char *contents, const char *tag) { - CatalogEntry *currentEntry = listHeader->next; - - while (currentEntry != currentEntry->next) { - if (!XtcStricmp(currentEntry->contents, contents)) { - if (!silent) - printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]); - return (currentEntry); - } - currentEntry = currentEntry->next; - } - return (NULL); -} + CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry)); + newEntry->files = 0; -/** - * Store information about a parameter file. The filename is added to the array - * of files with identical content. If not already present, in addition - * the content is stored - * - * \param entry existing entry to be updated - * \param path filename to add - * \param contents contents description - */ + if (contents) { + newEntry->contents = MyStrdup(contents); + } + if (tag) { + newEntry->tag = MyStrdup(tag); + } -static void -UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents) -{ - if (!entry->contents) { - entry->contents = strdup(contents); - } - - if (entry->files < MAXFILESPERCONTENT) { - entry->fullFileName[entry->files++] = strdup(path); - } else { - AbortProg("Number of file with same content too large!", NULL); - } + DL_INSERT_INORDER(catalog->head, newEntry, CompareEntries); + + return newEntry; } /** - * Create the list for the catalog entries + * Find an existing list element for a given content + * + * \param [in] catalog + * \param [in] contents contents to search. + * \param [in] silent we log error messages or not. * - * \return + * \returns CatalogEntry if found, NULL otherwise. */ static CatalogEntry * -CreateCatalog() +IsExistingContents(Catalog *catalog, const char *contents, BOOL_T silent) { - CatalogEntry *catalog = InitCatalog(); + CatalogEntry *head = catalog->head; + CatalogEntry *currentEntry; - return (catalog); + DL_FOREACH(head, currentEntry) { + if (!XtcStricmp(currentEntry->contents, contents)) { + if (!silent) { + printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]); + } + return (currentEntry); + } + } + return (NULL); } +/** + * Store information about a parameter file. The filename is added to the array + * of files with identical content. If not already present, in addition + * the content is stored + * + * \param entry existing entry to be updated + * \param path filename to add + * \param contents contents description + */ -static IndexEntry * -CreateIndexTable(unsigned int capacity) +EXPORT void +UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents, char *tag) { - IndexEntry *index = (IndexEntry *)malloc(capacity * sizeof(IndexEntry)); + if (!entry->contents) { + MyFree(entry->contents); + entry->contents = NULL; + } + if (contents) { + entry->contents = MyStrdup(contents); + } + + if (!entry->tag) { + MyFree(entry->tag); + entry->tag = NULL; + } + if (tag) { + entry->tag = MyStrdup(tag); + } - return (index); + CHECK( entry->files < MAXFILESPERCONTENT ); + entry->fullFileName[entry->files++] = MyStrdup(path); } /** @@ -245,7 +269,7 @@ CreateIndexTable(unsigned int capacity) * * \param dir IN opened directory handle * \param dirName IN name of directory - * \param fileName OUT fully qualified filename + * \param fileName OUT fully qualified filename, must be free()'d by caller * * \return TRUE if file found, FALSE if not */ @@ -253,97 +277,40 @@ CreateIndexTable(unsigned int capacity) static bool GetNextParameterFile(DIR *dir, const char *dirName, char **fileName) { - bool done = false; - bool res = false; - - /* - * get all files from the directory - */ - while (!done) { - struct stat fileState; - struct dirent *ent; - - ent = readdir(dir); - - if (ent) { - if (!XtcStricmp(FindFileExtension(ent->d_name), "xtp")) { - /* create full file name and get the state for that file */ - MakeFullpath(fileName, dirName, ent->d_name, NULL); - - if (stat(*fileName, &fileState) == -1) { - fprintf(stderr, "Error getting file state for %s\n", *fileName); - continue; - } - - /* ignore any directories */ - if (!(fileState.st_mode & S_IFDIR)) { - done = true; - res = true; - } - } - } else { - done = true; - res = false; - } - } - return (res); -} - -/** - * Scan a directory for parameter files. For each file found the CONTENTS is - * read and added to the list * - * - * \param insertAfter IN starting point for the list of files - * \param dirName IN directory to be scanned - * - * \return pointer to the last element(?) - */ - -static CatalogEntry * -ScanDirectory(CatalogEntry *catalog, const char *dirName) -{ - DIR *d; - CatalogEntry *newEntry = catalog; - - d = opendir(dirName); - if (d) { - char *fileName = NULL; - - while (GetNextParameterFile(d, dirName, &fileName)) { - CatalogEntry *existingEntry; - char *contents = GetParameterFileContent(fileName); - if ((existingEntry = IsExistingContents(catalog, contents,FALSE))) { - printf("Duplicate CONTENTS record in parameter file %s\n", fileName); - if (strcmp(existingEntry->fullFileName[existingEntry->files-1],fileName)) - UpdateCatalogEntry(existingEntry, fileName, contents); - } else { - newEntry = InsertInOrder(catalog,contents); - UpdateCatalogEntry(newEntry, fileName, contents); - } - free(contents); - free(fileName); - fileName = NULL; - } - closedir(d); - } - - return (newEntry); -} - -/** - * Comparison function for IndexEntries used by qsort() - * - * \param entry1 IN - * \param entry2 IN - * \return per C runtime conventions - */ + bool done = false; + bool res = false; + + /* + * get all files from the directory + */ + while (!done) { + struct stat fileState; + struct dirent *ent; + + ent = readdir(dir); + + if (ent) { + if (!XtcStricmp(FindFileExtension(ent->d_name), "xtp")) { + /* create full file name and get the state for that file */ + MakeFullpath(fileName, dirName, ent->d_name, NULL); + + if (stat(*fileName, &fileState) == -1) { + fprintf(stderr, "Error getting file state for %s\n", *fileName); + continue; + } -static int -CompareIndex(const void *entry1, const void *entry2) -{ - IndexEntry index1 = *(IndexEntry *)entry1; - IndexEntry index2 = *(IndexEntry *)entry2; - return (strcoll(index1.keyWord, index2.keyWord)); + /* ignore any directories */ + if (!(fileState.st_mode & S_IFDIR)) { + done = true; + res = true; + } + } + } else { + done = true; + res = false; + } + } + return (res); } /*! @@ -357,218 +324,185 @@ CompareIndex(const void *entry1, const void *entry2) bool FilterKeyword(char *word) { - if (strlen(word) == 1 && strpbrk(word, PUNCTUATION )) { - return(true); + if (strlen(word) == 1 && strpbrk(word, PUNCTUATION)) { + return (true); } - return(false); -} - -/** - * Create the keyword index from a list of parameter files - * - * \param catalog IN list of parameter files - * \param index IN index table to be filled - * \param pointer IN/OUT array of words that are indexed - * \param capacityOfIndex IN total maximum of keywords - * \return number of indexed keywords - */ -static unsigned -CreateContentsIndex(CatalogEntry *catalog, IndexEntry *index, void** words_array, - unsigned capacityOfIndex) -{ - CatalogEntry *currentEntry = catalog->next; - unsigned totalMemory = 0; - size_t wordCount = 0; - char *wordList; - char *wordListPtr; - - while (currentEntry != currentEntry->next) { - totalMemory += strlen(currentEntry->contents) + 1; - currentEntry = currentEntry->next; - } - - wordList = malloc((totalMemory + 1) * sizeof(char)); - *words_array = (void*)wordList; - - wordListPtr = wordList; - currentEntry = catalog->next; - - while (currentEntry != currentEntry->next) { - char *word; - char *content = strdup(currentEntry->contents); - - word = strtok(content, " \t\n\r"); - while (word && wordCount < capacityOfIndex) { - strcpy(wordListPtr, word); - - char *p = wordListPtr; - for (; *p; ++p) { - *p = tolower(*p); - } - if (!FilterKeyword(wordListPtr)) { - index[wordCount].value = currentEntry; - index[wordCount].keyWord = wordListPtr; - wordListPtr += strlen(word) + 1; - wordCount++; - if (wordCount >= capacityOfIndex) { - AbortProg("Too many keywords were used!", NULL); - } - } - word = strtok(NULL, " \t\n\r"); - } - free(content); - currentEntry = currentEntry->next; - } - *wordListPtr = '\0'; - qsort((void*)index, wordCount, sizeof(IndexEntry), CompareIndex); - - return (wordCount); + for (int i = 0; i < sizeof(stopwords) / sizeof(char *); i++) { + if (!XtcStricmp(word, stopwords+i)) { + return (true); + } + } + return (false); } -/** -* A recursive binary search function. It returns location of x in -* given array arr[l..r] is present, otherwise -1 -* Taken from http://www.geeksforgeeks.org/binary-search/ and modified -* -* \param arr IN array to search -* \param l IN starting index -* \param r IN highest index in array -* \param key IN key to search -* \return index if found, -1 otherwise -*/ - -static int SearchInIndex(IndexEntry arr[], int l, int r, char *key) +int KeyWordCmp(void *a, void *b) { - if (r >= l) { - int mid = l + (r - l) / 2; - int res = XtcStricmp(key, arr[mid].keyWord); - - // If the element is present at the middle itself - if (!res) { - return mid; - } - - // If the array size is 1 - if (r == 0) { - return -1; - } - - // If element is smaller than mid, then it can only be present - // in left subarray - if (res < 0) { - return SearchInIndex(arr, l, mid - 1, key); - } - - // Else the element can only be present in right subarray - return SearchInIndex(arr, mid + 1, r, key); - } - - // We reach here when element is not present in array - return -1; + return XtcStricmp(((IndexEntry *)a)->keyWord,((IndexEntry *)b)->keyWord); } + /** - * Inserts a key in arr[] of given capacity. n is current - * size of arr[]. This function returns n+1 if insertion - * is successful, else n. - * Taken from http ://www.geeksforgeeks.org/search-insert-and-delete-in-a-sorted-array/ and modified + * Standardize spelling: remove some typical spelling problems. It is assumed that the word + * has already been converted to lower case + * + * \param [in,out] word If non-null, the word. */ -int InsertSorted(CatalogEntry *arr[], int n, CatalogEntry *key, int capacity) +void +StandardizeSpelling(char *word) { - // Cannot insert more elements if n is already - // more than or equal to capcity - if (n >= capacity) { - return n; - } + char *p = strchr(word, '-'); + // remove the word 'scale' from combinations like N-scale + if (p) { + if (!XtcStricmp(p+1, "scale")) { + *p = '\0'; + } + } - int i; - for (i = n - 1; (i >= 0 && arr[i] > key); i--) { - arr[i + 1] = arr[i]; - } + if (!strncasecmp(word, "h0", 2)) { + strcpy(word, "ho"); + } - arr[i + 1] = key; + if (!strncasecmp(word, "00", 2)) { + strcpy(word, "oo"); + } - return (n + 1); + if (word[0] == '0') { + word[0] = 'o'; + } } /** - * Comparison function for CatalogEntries used by qsort() + * Create the keyword index from a list of parameter files * - * \param entry1 IN - * \param entry2 IN - * \return per C runtime conventions + * \param [in] library initialized library + * + * \returns number of indexed keywords. */ -static int -CompareResults(const void *entry1, const void *entry2) +static unsigned +CreateKeywordIndex(ParameterLib *library) { - CatalogEntry * index1 = *(CatalogEntry **)entry1; - CatalogEntry * index2 = *(CatalogEntry **)entry2; - return (strcoll(index1->contents, index2->contents)); -} + CatalogEntry *listOfEntries = library->catalog->head; + CatalogEntry *curParamFile; + size_t totalMemory = 0; + size_t wordCount = 0; + char *wordList; + char *wordListPtr; + IndexEntry *index = library->index; + + // allocate a buffer for the complete set of keywords + DL_FOREACH(listOfEntries, curParamFile) { + totalMemory += strlen(curParamFile->contents) + 1; + } + wordList = MyMalloc((totalMemory + 1) * sizeof(char)); -/** - * Search the index for a keyword. The index is assumed to be sorted. So after one entry - * is found, neighboring entries up and down are checked as well. The total result set - * is placed into an array and returned. This array has to be free'd by the caller. - * - * \param index IN index list - * \param length IN number of entries index - * \param search IN search string - * \param resultCount OUT count of found entries - * \return array of found catalog entries, NULL if none found - */ + wordListPtr = wordList; -static int findAll = 1; + DL_FOREACH(listOfEntries, curParamFile) { + char *word; + char *content = strdup(curParamFile->contents); -unsigned int -FindWord(IndexEntry *index, int length, char *search, CatalogEntry ***entries) -{ - CatalogEntry **result; //Array of pointers to Catalog Entries - int found; - int foundElements = 0; - *entries = NULL; - - //Get all the entries back for generic search or if "generic find" - if (findAll || !search || (search[0] == '*') || (search[0] == '\0')) { - result = malloc((length) * sizeof(CatalogEntry *)); - for (int i = 0; i < length; i++) { - result[i] = index[i].value; - } - *entries = result; - return length; - } + word = strtok(content, SEARCHDELIMITER); + while (word) { + strcpy(wordListPtr, word); - found = SearchInIndex(index, 0, length, search); + XtcStrlwr(wordListPtr); + if (!FilterKeyword(wordListPtr)) { + IndexEntry *searchEntry = MyMalloc(sizeof(IndexEntry)); + IndexEntry *existingEntry = NULL; + searchEntry->keyWord = wordListPtr; + StandardizeSpelling(wordListPtr); - if (found >= 0) { - int lower = found; - int upper = found; - int i; + if (index) { + DL_SEARCH(index, existingEntry, searchEntry, KeyWordCmp); + } + if (existingEntry) { + DYNARR_APPEND(CatalogEntry *, *(existingEntry->references), 5); + DYNARR_LAST(CatalogEntry *, *(existingEntry->references)) = curParamFile; + MyFree(searchEntry); + } else { + searchEntry->references = calloc(1, sizeof(dynArr_t)); + DYNARR_APPEND(CatalogEntry *, *(searchEntry->references), 5); + DYNARR_LAST(CatalogEntry *, *(searchEntry->references)) = curParamFile; + DL_APPEND(index, searchEntry); + LOG1(log_params, ("Index Entry: <%s>\n", searchEntry->keyWord)) + } - while (lower > 0 && !XtcStricmp(index[lower-1].keyWord, search)) { - lower--; - } + wordListPtr += strlen(word) + 1; + wordCount++; + } + word = strtok(NULL, SEARCHDELIMITER); + } + free(content); + } + *wordListPtr = '\0'; - while (upper < length - 1 && !XtcStricmp(index[upper + 1].keyWord, search)) { - upper++; - } + DL_SORT(index, KeyWordCmp); + library->index = index; + library->words = wordList; - foundElements = 1 + upper - lower; + IndexEntry *existingEntry; + DL_FOREACH(index, existingEntry) { + LOG1(log_params, ("Index Entry: <%s> Count: %d\n", existingEntry->keyWord, + existingEntry->references->cnt)); + } + return (unsigned)(wordCount); +} - result = malloc((foundElements) * sizeof(CatalogEntry *)); +/** + * Search the index for a keyword. The index is assumed to be sorted. Each + * keyword has one entry in the index list. + * + * \param [in] index index list. + * \param length number of entries index. + * \param [in] search search string. + * \param [out] entries array of found entry. + * + * \returns TRUE if found, FALSE otherwise. + */ - for (i = 0; i < foundElements; i++) { - result[i] = index[i+lower].value; - } +unsigned int +FindWord(IndexEntry *index, int length, char *search, IndexEntry **entries) +{ + IndexEntry *result = NULL; + + IndexEntry searchWord; + searchWord.keyWord = search; + + *entries = NULL; + + DL_SEARCH(index, result, &searchWord, KeyWordCmp); + if (!result) { + int maxdistance = 1; + while (maxdistance <= LDISTANCELIMIT && !result ) { + IndexEntry *current; +// size_t minDistance = LDISTANCELIMIT + 1; + int maxProbability = 0; + LOG1(log_params, ("Close match for: <%s> maxdistance: %d\n", search, + maxdistance)); + + DL_FOREACH(index, current) { + size_t ldist = levenshtein(search, current->keyWord); + LOG1(log_params, ("Distance of: <%s> is %d\n", current->keyWord, ldist)); + if (ldist == maxdistance) { + if (current->references->cnt > maxProbability) { + if (!result) { + result = MyMalloc(sizeof(IndexEntry)); + } + memcpy(result, current, sizeof(IndexEntry)); + maxProbability = current->references->cnt; + } + } + } - qsort((void*)result, foundElements, sizeof(void *), CompareResults); + maxdistance++; + } + } - *entries = result; - } - return (foundElements); + *entries = result; + return (result != NULL); } /** @@ -578,23 +512,38 @@ FindWord(IndexEntry *index, int length, char *search, CatalogEntry ***entries) * \return TRUE on success */ -TrackLibrary * +ParameterLib * InitLibrary(void) { - TrackLibrary *trackLib = malloc(sizeof(TrackLibrary)); + ParameterLib *trackLib = MyMalloc(sizeof(ParameterLib)); + + if (trackLib) { + trackLib->catalog = InitCatalog(); + trackLib->index = NULL; + trackLib->wordCount = 0; + trackLib->parameterFileCount = 0; + } + + return (trackLib); +} - if (trackLib) { - trackLib->catalog = CreateCatalog(); - trackLib->index = NULL; - trackLib->wordCount = 0; - trackLib->trackTypeCount = 0; - } +/** + * Destroys the library freeing all associated memory + * + * \param [in] library If non-null, the library. + */ - return (trackLib); +void +DestroyLibrary(ParameterLib *library) +{ + if (library) { + DestroyCatalog(library->catalog); + MyFree(library); + } } /** - * Scan directory and all parameter files found to the catalog + * Scan directory and add all parameter files found to the catalog * * \param trackLib IN the catalog * \param directory IN directory to scan @@ -602,78 +551,109 @@ InitLibrary(void) */ bool -GetTrackFiles(TrackLibrary *trackLib, char *directory) +CreateCatalogFromDir(ParameterLib *paramLib, char *directory) { - ScanDirectory(trackLib->catalog, directory); - trackLib->trackTypeCount = CountCatalogEntries(trackLib->catalog); + DIR *d; + Catalog *catalog = paramLib->catalog; + + d = opendir(directory); + if (d) { + char *fileName = NULL; + + while (GetNextParameterFile(d, directory, &fileName)) { + CatalogEntry *existingEntry; + + char *contents = GetParameterFileContent(fileName); + + char *scale = GetParameterFileScale(fileName); - return (trackLib->trackTypeCount); + + if ((existingEntry = IsExistingContents(catalog, contents, FALSE))) { + UpdateCatalogEntry(existingEntry, fileName, contents, scale); + } else { + CatalogEntry *newEntry; + newEntry = InsertInOrder(catalog, contents, scale); + UpdateCatalogEntry(newEntry, fileName, contents, scale); + } + MyFree(contents); + MyFree(scale); + free(fileName); + fileName = NULL; + } + closedir(d); + } + paramLib->parameterFileCount = CountCatalogEntries(paramLib->catalog); + return (paramLib->parameterFileCount); } + /** - * Add a list of parameters files to a catalog. This function is - * called when the user selects files in the file selector. + * Discard the complete catalog from a library * - * \param files IN count of files - * \param fileName IN array of filenames - * \param data IN pointer to the catalog - * \return alwqys TRUE + * \param [in] library */ -int GetParameterFileInfo( - int files, - char ** fileName, - void * data) +void +DiscardCatalog(ParameterLib *library) { - CatalogEntry *catalog = (CatalogEntry *)data; - - assert(fileName != NULL); - assert(files > 0); - assert(data != NULL); - - for (int i = 0; i < files; i++) { - CatalogEntry *newEntry; - char *contents = GetParameterFileContent(fileName[i]); - - if (!(newEntry = IsExistingContents(catalog, contents,TRUE))) { - newEntry = InsertIntoCatalogAfter(catalog); - } - UpdateCatalogEntry(newEntry, fileName[i], contents); - free(contents); - } - return (TRUE); + CatalogEntry *entry; + CatalogEntry *temp; + + DL_FOREACH_SAFE(library->catalog->head, entry, temp) { + MyFree(entry->contents); + MyFree(entry->tag); + for (unsigned int i = 0; i < entry->files; i++) { + MyFree(entry->fullFileName[i]); + } + DL_DELETE(library->catalog->head, entry); + MyFree(entry); + } + } + /** - * Create the search index from the contents description for the whole catalog. - * A fixed number of words are added to the index. See ESTIMATED_CONTENTS_WORDS + * Create the search index from the contents description for the whole + * catalog. * - * \param trackLib IN the catalog - * \return the number of words indexed + * \param [in] parameterLib IN the catalog. + * + * \returns the number of words indexed. */ unsigned -CreateLibraryIndex(TrackLibrary *trackLib) +CreateLibraryIndex(ParameterLib *parameterLib) { - trackLib->index = CreateIndexTable(trackLib->trackTypeCount * - ESTIMATED_CONTENTS_WORDS); + parameterLib->index = NULL; - trackLib->wordCount = CreateContentsIndex(trackLib->catalog, trackLib->index, - &trackLib->words_array, - ESTIMATED_CONTENTS_WORDS * trackLib->trackTypeCount); + parameterLib->wordCount = CreateKeywordIndex(parameterLib); - return (trackLib->wordCount); + return (parameterLib->wordCount); } +/** + * Discard library index freeing all memory used + * references were created using MakeFullPath. These were allocated using malloc and + * not MyMalloc + * + * \param [in] trackLib the track library. + */ + void -DeleteLibraryIndex(TrackLibrary *trackLib) +DiscardLibraryIndex(ParameterLib *trackLib) { - free(trackLib->index); - trackLib->index = NULL; - - free(trackLib->words_array); + IndexEntry *indexEntry; + IndexEntry *tmp; + + DL_FOREACH_SAFE(trackLib->index, indexEntry, tmp) { + DYNARR_FREE(CatalogEntry *, *(indexEntry->references)); + free(indexEntry->references); + DL_DELETE(trackLib->index, indexEntry); + MyFree(indexEntry); + } + MyFree(trackLib->words); + trackLib->index = NULL; trackLib->wordCount = 0; - } @@ -684,128 +664,307 @@ DeleteLibraryIndex(TrackLibrary *trackLib) * \return NULL if error or empty directory, else library handle */ -TrackLibrary * +ParameterLib * CreateLibrary(char *directory) { - TrackLibrary *library; + ParameterLib *library; - library = InitLibrary(); - if (library) { - if (!GetTrackFiles(library, directory)) { - return (NULL); - } + log_params = LogFindIndex("params"); - CreateLibraryIndex(library); - } - return (library); + library = InitLibrary(); + if (library) { + if (!CreateCatalogFromDir(library, directory)) { + return (NULL); + } + + CreateLibraryIndex(library); + } + return (library); } +/** + * Discard library freeing all memory used + * + * \param [in,out] library If non-null, the library. + */ + void -DeleteLibrary(TrackLibrary* library) +DiscardLibrary(ParameterLib* library) { - DeleteLibraryIndex(library); + CatalogEntry *entry = library->catalog->head; + CatalogEntry *element; + CatalogEntry *tmp; + DiscardLibraryIndex(library); - free(library); + DL_FOREACH_SAFE(entry, element, tmp) { + MyFree(element->contents); + MyFree(element->tag); + for (unsigned int i = 0; i < element->files; i++) { + MyFree(element->fullFileName[i]); + } + DL_DELETE(entry, element); + MyFree(element); + } + MyFree(library->words); + MyFree(library); } -// Case insensitive comparison -char* stristr( const char* haystack, const char* needle ) +/** + * Create a statistic for a finished search. The returned string has to be MyFreed() after usage + * + * \param [in] result the finished search + * + * \returns Null if it fails, else the found statistics. + */ + +char * +SearchStatistics(SearchResult *result) { - int c = tolower((unsigned char)*needle); - if (c == '\0') - return (char *)haystack; - for (; *haystack; haystack++) { - if (tolower((unsigned char)*haystack) == c) { - for (size_t i = 0;;) { - if (needle[++i] == '\0') - return (char *)haystack; - if (tolower((unsigned char)haystack[i]) != tolower((unsigned char)needle[i])) - break; - } - } - } - return NULL; + DynString buffer; + DynString subStats[STATE_COUNT]; + + unsigned searched = 0; + unsigned discarded = 0; + unsigned notfound = 0; + unsigned close = 0; + + char *resStat; + DynStringMalloc(&buffer, 16); + + for (int i = SEARCHED; i < STATE_COUNT; i++) { + DynStringMalloc(subStats + i, 16); + } + + DynStringCatCStr(subStats + SEARCHED, _("Found: ")); + DynStringCatCStr(subStats + CLOSE, _("Similar: ")); + DynStringCatCStr(subStats + DISCARDED, _("Ignored: ")); + DynStringCatCStr(subStats + NOTFOUND, _("Not found: ")); + + for (unsigned int i = 0; i < result->words; i++) { + switch (result->kw[i].state) { + case SEARCHED: + DynStringPrintf(&buffer, "%s (%d) ", result->kw[i].keyWord, + result->kw[i].count); + searched++; + break; + case DISCARDED: + DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord); + discarded++; + break; + case NOTFOUND: + DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord); + notfound++; + break; + case CLOSE: + DynStringPrintf(&buffer, "%s ", result->kw[i].keyWord); + close++; + break; + default: + break; + } + DynStringCatStr(subStats + result->kw[i].state, &buffer); + } + + DynStringReset(&buffer); + if (searched) { + DynStringCatStr(&buffer, subStats + SEARCHED); + } + if (close) { + DynStringCatStr(&buffer, subStats + CLOSE); + } + if (notfound) { + DynStringCatStr(&buffer, subStats + NOTFOUND); + } + if (discarded) { + DynStringCatStr(&buffer, subStats + DISCARDED); + } + + resStat = MyStrdup(DynStringToCStr(&buffer)); + DynStringFree(&buffer); + for (int i = SEARCHED; i < STATE_COUNT; i++) { + DynStringFree(subStats + i); + } + return (resStat); } /** - * Search the library for a keyword string and return the result list + * returns number of words in str. * - * First the index is searched for the first word and then each "hit" is matched - * to the entire search string + * \param [in] str the string. * - * Null, Blank and "*" match all entries + * \returns The total number of words. + */ + +unsigned countWords(char *str) +{ + int state = FALSE; + unsigned wc = 0; // word count + + // Scan all characters one by one + while (*str) { + // If next character is a separator, set the + // state as FALSE + if (*str == ' ' || *str == '\n' || *str == '\t' || *str == '\r' + || *str == '/') { + state = FALSE; + } + + // If next character is not a word separator and + // state is OUT, then set the state as IN and + // increment word count + else if (state == FALSE) { + state = TRUE; + ++wc; + } + + // Move to next character + ++str; + } + + return wc; +} + +/** + * Search the library for a keyword string and return the result list * - * The list is de-duped of repeat of filenames as the same file might appear in - * more than once + * Each key word exists only once in the index. * * \param library IN the library * \param searchExpression IN keyword to search for * \param resultEntries IN list header for result list * \return number of found entries */ + unsigned -SearchLibrary(TrackLibrary *library, char *searchExpression, - CatalogEntry *resultEntries) +SearchLibrary(ParameterLib *library, char *searchExpression, + SearchResult *results) { - CatalogEntry **entries; - CatalogEntry * newEntry = resultEntries; - unsigned entryCount; - - char * word; - - word = strdup(searchExpression); - - //word = strtok(word," \t"); - - if (library->index == NULL || library->wordCount == 0) { - return (0); - } - entryCount = FindWord(library->index, library->wordCount, word, - &entries); - int count= 0; - if (entryCount) { - unsigned int i = 0; - while (i < entryCount) { - char * match; - //Check if entire String Matches - if (!searchExpression || !word || (word[0] == '*') || (word[0] == '\0') || - (match = stristr(entries[i]->contents,searchExpression))) { - CatalogEntry * existingEntry; - existingEntry = IsExistingContents(resultEntries, entries[i]->contents, TRUE); - //Same FileName already in one of the entries? - BOOL_T found = FALSE; - if (existingEntry) { - for (unsigned int j=0;j<existingEntry->files;j++) { - if (!strcmp(existingEntry->fullFileName[j],entries[i]->fullFileName[entries[i]->files-1])) { - found=TRUE; + CatalogEntry *element; + IndexEntry *entries; +// unsigned entryCount = 0; + char *searchWord; + unsigned words = countWords(searchExpression); + char *searchExp = MyStrdup(searchExpression); + unsigned i = 0; + + if (library->index == NULL || library->wordCount == 0) { + return (0); + } + + results->kw = MyMalloc(words * sizeof(struct sSingleResult)); + results->subCatalog.head = NULL; + + searchWord = strtok(searchExp, SEARCHDELIMITER); + while (searchWord) { + XtcStrlwr(searchWord); + if (!FilterKeyword(searchWord)) { + StandardizeSpelling(searchWord); + results->kw[i].state = SEARCHED; + } else { + results->kw[i].state = DISCARDED; + } + results->kw[i++].keyWord = MyStrdup(searchWord); + searchWord = strtok(NULL, SEARCHDELIMITER); + } + results->words = words; + + i = 0; + while (i < words) { + if (results->kw[i].state == DISCARDED) { + i++; + continue; + } + FindWord(library->index, library->wordCount, results->kw[i].keyWord, &entries); + if (entries) { + results->kw[i].count = entries->references->cnt; + if (XtcStricmp(results->kw[i].keyWord, entries->keyWord)) { + results->kw[i].state = CLOSE; + MyFree(results->kw[i].keyWord); + results->kw[i].keyWord = MyStrdup(entries->keyWord); + } + + if (results->subCatalog.head == NULL) { + // if first keyword -> initialize result set + for (int j = 0; j < entries->references->cnt; j++) { + CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry)); + CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j); + newEntry->contents = MyStrdup(foundEntry->contents); + newEntry->tag = MyStrdup(foundEntry->tag); + newEntry->files = foundEntry->files; + for (unsigned int i=0; i<newEntry->files; i++) { + newEntry->fullFileName[i] = MyStrdup(foundEntry->fullFileName[i]); + } + + DL_APPEND(results->subCatalog.head, newEntry); + } + } else { + // follow up keyword, create intersection with current result set + CatalogEntry *current; + CatalogEntry *temp; + + DL_FOREACH_SAFE(results->subCatalog.head, current, temp) { + int found = 0; + for (int j = 0; j < entries->references->cnt; j++) { + CatalogEntry *foundEntry = DYNARR_N(CatalogEntry *, *(entries->references), j); + + if (strcmp(foundEntry->contents,current->contents)==0) { + found = TRUE; break; } } - if (found == TRUE ) { - i++; - continue; + if (!found) { + DL_DELETE(results->subCatalog.head, current); + MyFree(current->contents); + MyFree(current->tag); + for (unsigned int i=0; i<current->files; i++) { + MyFree(current->fullFileName[i]); + } + MyFree(current); } - UpdateCatalogEntry(existingEntry, entries[i]->fullFileName[(entries[i]->files- 1)], - entries[i]->contents); - } else { - newEntry = InsertInOrder(resultEntries,entries[i]->contents); - UpdateCatalogEntry(newEntry, entries[i]->fullFileName[(entries[i]->files- 1)], - entries[i]->contents); } - count++; - } - i++; - } - } - free(word); - if (entries) - free(entries); //Clean-up after search - return (count); + } + } else { + // Searches that don't yield a result are ignored + results->kw[i].state = NOTFOUND; + results->kw[i].count = 0; + } + i++; + } + + DL_COUNT(results->subCatalog.head, element, results->totalFound); + MyFree(searchExp); + return (results->totalFound); +} + +/** + * Discard results. The memory allocated with the search is freed + * + * \param [in] res If non-null, the results. + */ + +void +SearchDiscardResult(SearchResult *res) +{ + if (res) { + CatalogEntry *current = res->subCatalog.head; + CatalogEntry *element; + CatalogEntry *tmp; + + DL_FOREACH_SAFE(current, element, tmp) { + DL_DELETE(current, element); + MyFree(element); + } + + for (unsigned int i = 0; i < res->words; i++) { + MyFree(res->kw[i].keyWord); + } + MyFree(res->kw); + } } /** - * Get the contents description from a parameter file. Returned string has to be freed after use. + * Get the contents description from a parameter file. Returned string has to be MyFree'd after use. * * \param file IN xtpfile * \return pointer to found contents or NULL if not present @@ -829,10 +988,10 @@ GetParameterFileContent(char *file) /* if found, store the rest of the line and the filename */ ptr = ptr+strlen(CONTENTSCOMMAND)+1; ptr = strtok(ptr, "\r\n"); - result = strdup(ptr); -#ifdef WINDOWS + result = MyStrdup(ptr); +#ifdef UTFCONVERT ConvertUTF8ToSystem(result); -#endif // WINDOWS +#endif // UTFCONVERT found = true; } } else { @@ -842,5 +1001,105 @@ GetParameterFileContent(char *file) } fclose(fh); } - return(result); + return (result); +} + +/** + * Get the first scale values from a parameter file. Returned strings have to be MyFreed after use + * + * \param file IN xtpfile + * \param array of one of three char results (Track, Structure and Car) + */ + +char * +GetParameterFileScale(char *file) +{ + FILE *fh; + char *scale = NULL; + + + fh = fopen(file, "rt"); + if (fh) { + bool found = FALSE, found_Turnout = FALSE, found_Structure = FALSE, + found_Car = FALSE; + + while (!found) { + char buffer[512]; + if (fgets(buffer, sizeof(buffer), fh)) { + char *ptr = strtok(buffer, " \t"); + if (!found_Turnout && !XtcStricmp(ptr, TURNOUTCOMMAND)) { + /* if found, store the rest of the line and the filename */ + ptr = ptr+strlen(TURNOUTCOMMAND)+1; + ptr = strtok(ptr, " \t"); + scale = MyMalloc(strlen(TURNOUTCOMMAND)+2+strlen(ptr)); + strcpy(scale,TURNOUTCOMMAND); + char * cp = scale + strlen(TURNOUTCOMMAND); + cp[0] = ' '; + cp++; + strcpy(cp,ptr); + found_Turnout = true; + } else if (!found_Structure && !XtcStricmp(ptr, STRUCTURECOMMAND)) { + /* if found, store the rest of the line and the filename */ + ptr = ptr+strlen(STRUCTURECOMMAND)+1; + ptr = strtok(ptr, " \t"); + scale = MyMalloc(strlen(STRUCTURECOMMAND)+2+strlen(ptr)); + strcpy(scale,STRUCTURECOMMAND); + char * cp = scale + strlen(STRUCTURECOMMAND)+1; + cp[-1] = ' '; + strcpy(cp,ptr); + found_Structure = true; + } else if (!found_Car && !XtcStricmp(ptr, CARCOMMAND)) { + /* if found, store the rest of the line and the filename */ + ptr = ptr+strlen(CARCOMMAND)+1; + ptr = strtok(ptr, " \t"); + scale = MyMalloc(strlen(CARCOMMAND)+2+strlen(ptr)); + strcpy(scale,CARCOMMAND); + char * cp = scale + strlen(CARCOMMAND)+1; + cp[-1] = ' '; + strcpy(cp,ptr); + found_Car = true; + } else if (!found_Car && !XtcStricmp(ptr, CARPROTOCOMMAND)) { + /* if found, store the rest of the line and the filename */ + scale = MyMalloc(strlen(CARPROTOCOMMAND)+3); + strcpy(scale,CARPROTOCOMMAND); + char * cp = scale + strlen(CARPROTOCOMMAND); + strcpy(cp," *"); + found_Car = true; + } + } else { + if (!found_Turnout && !found_Structure && !found_Car) { + fprintf(stderr, "Nothing found in %s\n", file); + found = true; + } + } + if (found_Turnout || found_Structure || found_Car) { found = TRUE; } + } + fclose(fh); + } + return scale; + +} + +#ifdef MEMWATCH +/** this is used to test for memory leaks. It should show no leaks from functions in this source file */ +RunMemoryTest(char *directory) +{ + ParameterLib *library; + SearchResult *results; + + mwInit(); + library = InitLibrary(); + if (library) { + CreateCatalogFromDir(library, directory); + CreateLibraryIndex(library); + results = MyMalloc(sizeof(SearchResult)); + SearchLibrary(library, "peco", results); + SearchDiscardResult(results); + MyFree(results); + DiscardLibraryIndex(library); + DiscardCatalog(library); + } + DestroyLibrary(library); + mwTerm(); } +#endif //MEMWATCH |