/** \file partcatalog.c * Manage the catalog of track parameter files */ /* XTrackCAD - Model Railroad CAD * Copyright (C) 2019 Martin Fischer * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #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" #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 * * \return pointer to first element */ Catalog * InitCatalog(void) { 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); } catalog->head = NULL; } /** * Create a new CatalogEntry and add it to the linked list. The newly * created entry is inserted into the list after the given position * * \param entry IN insertion point * \return pointer to new entry */ static CatalogEntry * InsertIntoCatalogAfter(CatalogEntry *entry) { 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 * * \param catalog IN * \return the number of elements */ unsigned CountCatalogEntries(Catalog *catalog) { CatalogEntry * entry; unsigned count = 0; DL_COUNT(catalog->head, entry, count); return (count); } /** * Empty a catalog. All data nodes including their allocated memory are freed. On * * \param listHeader IN the list */ EXPORT void CatalogDiscard(Catalog *catalog) { 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; } /** * Compare entries * * \param [in] a If non-null, a CatalogEntry to compare. * \param [in] b If non-null, a CatalogEntry to compare. * * \returns An int. */ static int CompareEntries(CatalogEntry *a, CatalogEntry *b) { return XtcStricmp(a->contents, b->contents); } /** * 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; } /** * 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. * * \returns CatalogEntry if found, NULL otherwise. */ static CatalogEntry * IsExistingContents(Catalog *catalog, const char *contents, BOOL_T silent) { CatalogEntry *head = catalog->head; CatalogEntry *currentEntry; 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 */ EXPORT void UpdateCatalogEntry(CatalogEntry *entry, char *path, char *contents, char *tag) { 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); if (entry->files < MAXFILESPERCONTENT) { entry->fullFileName[entry->files++] = MyStrdup(path); } else { AbortProg("Number of files with same content too large!", NULL); } } /** * 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, must be free()'d by caller * * \return TRUE if file found, FALSE if not */ 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); } /*! * 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 */ bool FilterKeyword(char *word) { if (strlen(word) == 1 && strpbrk(word, PUNCTUATION)) { return (true); } for (int i = 0; i < sizeof(stopwords) / sizeof(char *); i++) { if (!XtcStricmp(word, stopwords+i)) { return (true); } } return (false); } int KeyWordCmp(void *a, void *b) { return XtcStricmp(((IndexEntry *)a)->keyWord,((IndexEntry *)b)->keyWord); } /** * 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. */ void StandardizeSpelling(char *word) { char *p = strchr(word, '-'); // remove the word 'scale' from combinations like N-scale if (p) { if (!XtcStricmp(p+1, "scale")) { *p = '\0'; } } if (!strncasecmp(word, "h0", 2)) { strncpy(word, "ho", 2); } 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 [in] library initialized library * * \returns number of indexed keywords. */ static unsigned CreateKeywordIndex(ParameterLib *library) { 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)); wordListPtr = wordList; DL_FOREACH(listOfEntries, curParamFile) { char *word; char *content = strdup(curParamFile->contents); word = strtok(content, SEARCHDELIMITER); while (word) { strcpy(wordListPtr, word); 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++; } word = strtok(NULL, SEARCHDELIMITER); } free(content); } *wordListPtr = '\0'; DL_SORT(index, KeyWordCmp); library->index = index; library->words = wordList; IndexEntry *existingEntry; DL_FOREACH(index, existingEntry) { LOG1(log_params, ("Index Entry: <%s> Count: %d\n", existingEntry->keyWord, existingEntry->references->cnt)); } return (unsigned)(wordCount); } /** * 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. */ 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; } } } maxdistance++; } } *entries = result; return (result != NULL); } /** * Create and initialize the data structure for the track library * * \param trackLibrary OUT the newly allocated track library * \return TRUE on success */ ParameterLib * InitLibrary(void) { ParameterLib *trackLib = MyMalloc(sizeof(ParameterLib)); if (trackLib) { trackLib->catalog = InitCatalog(); trackLib->index = NULL; trackLib->wordCount = 0; trackLib->parameterFileCount = 0; } return (trackLib); } /** * Destroys the library freeing all associated memory * * \param [in] library If non-null, the library. */ void DestroyLibrary(ParameterLib *library) { if (library) { DestroyCatalog(library->catalog); MyFree(library); } } /** * 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 */ bool CreateCatalogFromDir(ParameterLib *paramLib, char *directory) { 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); 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); } /** * Discard the complete catalog from a library * * \param [in] library */ void DiscardCatalog(ParameterLib *library) { 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. * * \param [in] parameterLib IN the catalog. * * \returns the number of words indexed. */ unsigned CreateLibraryIndex(ParameterLib *parameterLib) { parameterLib->index = NULL; parameterLib->wordCount = CreateKeywordIndex(parameterLib); 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 DiscardLibraryIndex(ParameterLib *trackLib) { 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; } /** * Create the library and index of parameter files in a given directory. * * \param directory IN directory to scan * \return NULL if error or empty directory, else library handle */ ParameterLib * CreateLibrary(char *directory) { ParameterLib *library; log_params = LogFindIndex("params"); 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 DiscardLibrary(ParameterLib* library) { CatalogEntry *entry = library->catalog->head; CatalogEntry *element; CatalogEntry *tmp; DiscardLibraryIndex(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); } /** * 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) { 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); } /** * returns number of words in str. * * \param [in] str the string. * * \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 * * 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(ParameterLib *library, char *searchExpression, SearchResult *results) { 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;ifiles;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;ifiles;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++; } 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 MyFree'd after use. * * \param file IN xtpfile * \return pointer to found contents or NULL if not present */ 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 = 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