diff options
Diffstat (limited to 'app/bin/partcatalog.c')
-rw-r--r-- | app/bin/partcatalog.c | 1250 |
1 files changed, 752 insertions, 498 deletions
diff --git a/app/bin/partcatalog.c b/app/bin/partcatalog.c index a1db09c..25ae024 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 @@ -19,41 +19,27 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "misc2.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,22 +47,46 @@ * \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, *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; } /** @@ -90,32 +100,30 @@ InitCatalog(void) static CatalogEntry * InsertIntoCatalogAfter(CatalogEntry *entry) { - CatalogEntry *newEntry = (CatalogEntry *)malloc(sizeof(CatalogEntry)); + 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); } /** - * 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; + CatalogEntry * entry; unsigned count = 0; - - while (currentEntry != currentEntry->next) { - count++; - currentEntry = currentEntry->next; - } + DL_COUNT(catalog->head, entry, count); return (count); } @@ -125,70 +133,96 @@ 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,*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; + return XtcStricmp(a->contents, b->contents); +} - while (currentEntry->next != currentEntry->next->next) { - CatalogEntry *nextEntry = currentEntry->next; - if (XtcStricmp(nextEntry->contents, contents)>0) { - return InsertIntoCatalogAfter(currentEntry); - } - currentEntry = nextEntry; - } - return InsertIntoCatalogAfter(currentEntry); +/** + * Create a new CatalogEntry and insert it keeping the list sorted + * + * \param [in] catalog + * \param [in] contents to include. + * \param [in] tag + * + * \returns CatalogEntry + */ + +EXPORT CatalogEntry * +InsertInOrder(Catalog *catalog, const char *contents, const char *tag) +{ + CatalogEntry *newEntry = MyMalloc(sizeof(CatalogEntry)); + newEntry->files = 0; + + if (contents) + newEntry->contents = MyStrdup(contents); + if (tag) + newEntry->tag = MyStrdup(tag); + + DL_INSERT_INORDER(catalog->head, newEntry, CompareEntries); + + return newEntry; } + /** - * Get the existing list element for a content + * Find an existing list element for a given content * - * \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 contents to search. + * \param [in] silent we log error messages or not. * - * \return CatalogEntry if found, NULL otherwise + * \returns CatalogEntry if found, NULL otherwise. */ static CatalogEntry * -IsExistingContents(CatalogEntry *listHeader, const char *contents, BOOL_T silent) +IsExistingContents(Catalog *catalog, const char *contents, BOOL_T silent) { - CatalogEntry *currentEntry = listHeader->next; + CatalogEntry *head = catalog->head; + CatalogEntry *currentEntry; - while (currentEntry != currentEntry->next) { + DL_FOREACH(head, currentEntry) { if (!XtcStricmp(currentEntry->contents, contents)) { - if (!silent) - printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]); + if (!silent) { + printf("%s already exists in %s\n", contents, currentEntry->fullFileName[0]); + } return (currentEntry); } - currentEntry = currentEntry->next; } return (NULL); } @@ -203,49 +237,36 @@ IsExistingContents(CatalogEntry *listHeader, const char *contents, BOOL_T silent * \param contents contents description */ -static void -UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents) +EXPORT void +UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents, char *tag) { if (!entry->contents) { - entry->contents = strdup(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); if (entry->files < MAXFILESPERCONTENT) { - entry->fullFileName[entry->files++] = strdup(path); + entry->fullFileName[entry->files++] = MyStrdup(path); } else { - AbortProg("Number of file with same content too large!", NULL); + AbortProg("Number of files with same content too large!", NULL); } } /** - * Create the list for the catalog entries - * - * \return - */ - -static CatalogEntry * -CreateCatalog() -{ - CatalogEntry *catalog = InitCatalog(); - - return (catalog); -} - - -static IndexEntry * -CreateIndexTable(unsigned int capacity) -{ - IndexEntry *index = (IndexEntry *)malloc(capacity * sizeof(IndexEntry)); - - return (index); -} - -/** * Scan opened directory for the next parameter file * * \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 */ @@ -289,391 +310,347 @@ GetNextParameterFile(DIR *dir, const char *dirName, char **fileName) 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 +/*! + * Filter keywords. Current rules: + * - single character string that only consist of a punctuation char * - * \return pointer to the last element(?) + * \param word IN keyword + * \return true if any rule applies, false otherwise */ -static CatalogEntry * -ScanDirectory(CatalogEntry *catalog, const char *dirName) +bool +FilterKeyword(char *word) { - DIR *d; - CatalogEntry *newEntry = catalog; - - d = opendir(dirName); - if (d) { - char *fileName = NULL; + if (strlen(word) == 1 && strpbrk(word, PUNCTUATION)) { + return (true); + } - 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; + for (int i = 0; i < sizeof(stopwords) / sizeof(char *); i++) { + if (!XtcStricmp(word, stopwords+i)) { + return (true); } - closedir(d); } + return (false); +} - return (newEntry); +int KeyWordCmp(void *a, void *b) +{ + return XtcStricmp(((IndexEntry *)a)->keyWord,((IndexEntry *)b)->keyWord); } + /** - * Comparison function for IndexEntries used by qsort() + * Standardize spelling: remove some typical spelling problems. It is assumed that the word + * has already been converted to lower case * - * \param entry1 IN - * \param entry2 IN - * \return per C runtime conventions + * \param [in,out] word If non-null, the word. */ -static int -CompareIndex(const void *entry1, const void *entry2) +void +StandardizeSpelling(char *word) { - IndexEntry index1 = *(IndexEntry *)entry1; - IndexEntry index2 = *(IndexEntry *)entry2; - return (strcoll(index1.keyWord, index2.keyWord)); -} + char *p = strchr(word, '-'); + // remove the word 'scale' from combinations like N-scale + if (p) { + if (!XtcStricmp(p+1, "scale")) { + *p = '\0'; + } + } -/*! - * Filter keywords. Current rules: - * - single character string that only consist of a punctuation char - * - * \param word IN keyword - * \return true if any rule applies, false otherwise - */ + if (!strncasecmp(word, "h0", 2)) { + strncpy(word, "ho", 2); + } -bool -FilterKeyword(char *word) -{ - if (strlen(word) == 1 && strpbrk(word, PUNCTUATION )) { - return(true); - } - return(false); + if (!strncasecmp(word, "00", 2)) { + strncpy(word, "oo", 2); + } + + if (word[0] == '0') { + word[0] = 'o'; + } } /** * 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 + * \param [in] library initialized library + * + * \returns number of indexed keywords. */ + static unsigned -CreateContentsIndex(CatalogEntry *catalog, IndexEntry *index, void** words_array, - unsigned capacityOfIndex) +CreateKeywordIndex(ParameterLib *library) { - CatalogEntry *currentEntry = catalog->next; - unsigned totalMemory = 0; + CatalogEntry *listOfEntries = library->catalog->head; + CatalogEntry *curParamFile; + size_t totalMemory = 0; size_t wordCount = 0; char *wordList; char *wordListPtr; + IndexEntry *index = library->index; - while (currentEntry != currentEntry->next) { - totalMemory += strlen(currentEntry->contents) + 1; - currentEntry = currentEntry->next; + // allocate a buffer for the complete set of keywords + DL_FOREACH(listOfEntries, curParamFile) { + totalMemory += strlen(curParamFile->contents) + 1; } - - wordList = malloc((totalMemory + 1) * sizeof(char)); - *words_array = (void*)wordList; + wordList = MyMalloc((totalMemory + 1) * sizeof(char)); wordListPtr = wordList; - currentEntry = catalog->next; - while (currentEntry != currentEntry->next) { + DL_FOREACH(listOfEntries, curParamFile) { char *word; - char *content = strdup(currentEntry->contents); + char *content = strdup(curParamFile->contents); - word = strtok(content, " \t\n\r"); - while (word && wordCount < capacityOfIndex) { + word = strtok(content, SEARCHDELIMITER); + while (word) { strcpy(wordListPtr, word); - char *p = wordListPtr; - for (; *p; ++p) { - *p = tolower(*p); + XtcStrlwr(wordListPtr); + if (!FilterKeyword(wordListPtr)) { + IndexEntry *searchEntry = MyMalloc(sizeof(IndexEntry)); + IndexEntry *existingEntry = NULL; + searchEntry->keyWord = wordListPtr; + StandardizeSpelling(wordListPtr); + + 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)) + } + + wordListPtr += strlen(word) + 1; + wordCount++; } - 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"); + word = strtok(NULL, SEARCHDELIMITER); } free(content); - currentEntry = currentEntry->next; } *wordListPtr = '\0'; - qsort((void*)index, wordCount, sizeof(IndexEntry), CompareIndex); + DL_SORT(index, KeyWordCmp); + library->index = index; + library->words = wordList; - return (wordCount); + IndexEntry *existingEntry; + DL_FOREACH(index, existingEntry) { + LOG1(log_params, ("Index Entry: <%s> Count: %d\n", existingEntry->keyWord, + existingEntry->references->cnt)); + } + return (unsigned)(wordCount); } /** -* 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 -*/ + * 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. + */ -static int SearchInIndex(IndexEntry arr[], int l, int r, char *key) +unsigned int +FindWord(IndexEntry *index, int length, char *search, IndexEntry **entries) { - if (r >= l) { - int mid = l + (r - l) / 2; - int res = XtcStricmp(key, arr[mid].keyWord); + IndexEntry *result = NULL; - // If the element is present at the middle itself - if (!res) { - return mid; - } - - // If the array size is 1 - if (r == 0) { - return -1; - } + IndexEntry searchWord; + searchWord.keyWord = search; - // 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); - } + *entries = NULL; - // Else the element can only be present in right subarray - return SearchInIndex(arr, mid + 1, r, key); - } + 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; + } + } + } - // We reach here when element is not present in array - return -1; + maxdistance++; + } + } + + *entries = result; + return (result != NULL); } /** - * 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 + * Create and initialize the data structure for the track library + * + * \param trackLibrary OUT the newly allocated track library + * \return TRUE on success */ -int InsertSorted(CatalogEntry *arr[], int n, CatalogEntry *key, int capacity) +ParameterLib * +InitLibrary(void) { - // Cannot insert more elements if n is already - // more than or equal to capcity - if (n >= capacity) { - return n; - } + ParameterLib *trackLib = MyMalloc(sizeof(ParameterLib)); - int i; - for (i = n - 1; (i >= 0 && arr[i] > key); i--) { - arr[i + 1] = arr[i]; + if (trackLib) { + trackLib->catalog = InitCatalog(); + trackLib->index = NULL; + trackLib->wordCount = 0; + trackLib->parameterFileCount = 0; } - arr[i + 1] = key; - - return (n + 1); + return (trackLib); } /** - * Comparison function for CatalogEntries used by qsort() + * Destroys the library freeing all associated memory * - * \param entry1 IN - * \param entry2 IN - * \return per C runtime conventions + * \param [in] library If non-null, the library. */ -static int -CompareResults(const void *entry1, const void *entry2) +void +DestroyLibrary(ParameterLib *library) { - CatalogEntry * index1 = *(CatalogEntry **)entry1; - CatalogEntry * index2 = *(CatalogEntry **)entry2; - return (strcoll(index1->contents, index2->contents)); + if (library) { + DestroyCatalog(library->catalog); + MyFree(library); + } } /** - * 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 + * Scan directory and add all parameter files found to the catalog + * + * \param trackLib IN the catalog + * \param directory IN directory to scan + * \return number of files found */ -static int findAll = 1; - -unsigned int -FindWord(IndexEntry *index, int length, char *search, CatalogEntry ***entries) +bool +CreateCatalogFromDir(ParameterLib *paramLib, char *directory) { - 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; - } - - found = SearchInIndex(index, 0, length, search); + DIR *d; + Catalog *catalog = paramLib->catalog; - if (found >= 0) { - int lower = found; - int upper = found; - int i; + d = opendir(directory); + if (d) { + char *fileName = NULL; - while (lower > 0 && !XtcStricmp(index[lower-1].keyWord, search)) { - lower--; - } + while (GetNextParameterFile(d, directory, &fileName)) { + CatalogEntry *existingEntry; - while (upper < length - 1 && !XtcStricmp(index[upper + 1].keyWord, search)) { - upper++; - } + char *contents = GetParameterFileContent(fileName); - foundElements = 1 + upper - lower; + char *scale = GetParameterFileScale(fileName); - result = malloc((foundElements) * sizeof(CatalogEntry *)); - for (i = 0; i < foundElements; i++) { - result[i] = index[i+lower].value; + 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; } - - qsort((void*)result, foundElements, sizeof(void *), CompareResults); - - *entries = result; + closedir(d); } - return (foundElements); + paramLib->parameterFileCount = CountCatalogEntries(paramLib->catalog); + return (paramLib->parameterFileCount); } /** - * Create and initialize the data structure for the track library + * Discard the complete catalog from a library * - * \param trackLibrary OUT the newly allocated track library - * \return TRUE on success + * \param [in] library */ -TrackLibrary * -InitLibrary(void) +void +DiscardCatalog(ParameterLib *library) { - TrackLibrary *trackLib = malloc(sizeof(TrackLibrary)); - - if (trackLib) { - trackLib->catalog = CreateCatalog(); - trackLib->index = NULL; - trackLib->wordCount = 0; - trackLib->trackTypeCount = 0; + 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); } - return (trackLib); } -/** - * Scan directory and all parameter files found to the catalog - * - * \param trackLib IN the catalog - * \param directory IN directory to scan - * \return number of files found - */ - -bool -GetTrackFiles(TrackLibrary *trackLib, char *directory) -{ - ScanDirectory(trackLib->catalog, directory); - trackLib->trackTypeCount = CountCatalogEntries(trackLib->catalog); - return (trackLib->trackTypeCount); -} /** - * Add a list of parameters files to a catalog. This function is - * called when the user selects files in the file selector. + * Create the search index from the contents description for the whole + * catalog. + * + * \param [in] parameterLib IN the catalog. * - * \param files IN count of files - * \param fileName IN array of filenames - * \param data IN pointer to the catalog - * \return alwqys TRUE + * \returns the number of words indexed. */ -int GetParameterFileInfo( - int files, - char ** fileName, - void * data) +unsigned +CreateLibraryIndex(ParameterLib *parameterLib) { - CatalogEntry *catalog = (CatalogEntry *)data; + parameterLib->index = NULL; - assert(fileName != NULL); - assert(files > 0); - assert(data != NULL); + parameterLib->wordCount = CreateKeywordIndex(parameterLib); - 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); + return (parameterLib->wordCount); } /** - * 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 + * Discard library index freeing all memory used + * references were created using MakeFullPath. These were allocated using malloc and + * not MyMalloc * - * \param trackLib IN the catalog - * \return the number of words indexed + * \param [in] trackLib the track library. */ -unsigned -CreateLibraryIndex(TrackLibrary *trackLib) -{ - trackLib->index = CreateIndexTable(trackLib->trackTypeCount * - ESTIMATED_CONTENTS_WORDS); - - trackLib->wordCount = CreateContentsIndex(trackLib->catalog, trackLib->index, - &trackLib->words_array, - ESTIMATED_CONTENTS_WORDS * trackLib->trackTypeCount); - - return (trackLib->wordCount); -} - void -DeleteLibraryIndex(TrackLibrary *trackLib) +DiscardLibraryIndex(ParameterLib *trackLib) { - free(trackLib->index); - trackLib->index = NULL; - - free(trackLib->words_array); - - trackLib->wordCount = 0; + 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,14 +661,16 @@ DeleteLibraryIndex(TrackLibrary *trackLib) * \return NULL if error or empty directory, else library handle */ -TrackLibrary * +ParameterLib * CreateLibrary(char *directory) { - TrackLibrary *library; + ParameterLib *library; + + log_params = LogFindIndex("params"); library = InitLibrary(); if (library) { - if (!GetTrackFiles(library, directory)) { + if (!CreateCatalogFromDir(library, directory)) { return (NULL); } @@ -700,112 +679,288 @@ CreateLibrary(char *directory) 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"); + 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); } - 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; - break; - } - } - if (found == TRUE ) { - i++; - continue; - } - 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++; - } + + 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) { + 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); + } + } + } + } else { + // Searches that don't yield a result are ignored + results->kw[i].state = NOTFOUND; + results->kw[i].count = 0; + } + i++; } - free(word); - if (entries) - free(entries); //Clean-up after search - return (count); + + DL_COUNT(results->subCatalog.head, element, results->totalFound); + MyFree(searchExp); + return (results->totalFound); } /** - * Get the contents description from a parameter file. Returned string has to be freed after use. + * 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 MyFree'd after use. * * \param file IN xtpfile * \return pointer to found contents or NULL if not present @@ -814,33 +969,132 @@ SearchLibrary(TrackLibrary *library, char *searchExpression, char * GetParameterFileContent(char *file) { - FILE *fh; - char *result = NULL; - - fh = fopen(file, "rt"); - if (fh) { - bool found = false; - - while (!found) { - char buffer[512]; - if (fgets(buffer, sizeof(buffer), fh)) { - char *ptr = strtok(buffer, " \t"); - if (!XtcStricmp(ptr, CONTENTSCOMMAND)) { - /* 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 - ConvertUTF8ToSystem(result); -#endif // WINDOWS - found = true; - } - } else { - fprintf(stderr, "Nothing found in %s\n", file); - found = true; - } - } - fclose(fh); - } - return(result); + FILE *fh; + char *result = NULL; + + fh = fopen(file, "rt"); + if (fh) { + bool found = false; + + while (!found) { + char buffer[512]; + if (fgets(buffer, sizeof(buffer), fh)) { + char *ptr = strtok(buffer, " \t"); + if (!XtcStricmp(ptr, CONTENTSCOMMAND)) { + /* if found, store the rest of the line and the filename */ + ptr = ptr+strlen(CONTENTSCOMMAND)+1; + ptr = strtok(ptr, "\r\n"); + result = MyStrdup(ptr); +#ifdef UTFCONVERT + ConvertUTF8ToSystem(result); +#endif // UTFCONVERT + found = true; + } + } else { + fprintf(stderr, "Nothing found in %s\n", file); + found = true; + } + } + fclose(fh); + } + 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 |