This is PAStringList.m in view mode; [Download] [Up]
#import "PAStringList.h" #import <sys/dir.h> /****************************************************************************** PAStringList The PAStringList (a subclass of Storage) is a convenient way to deal with lists of character strings. It contains methods for adding strings, inserting strings, sorting strings and searching for strings. In addition the PAStringList object has methods for generating lists of filenames from a given directory. There is also a specific method for searching the standard libraries (/NextLibrary, /LocalLibrary, ~/Libary). Files can be limited to particular extensions and can be returned with or without their full path and extension. The PAStringList can also add strings from a delimited string (such as the ones passed to an app by the workspace). Finally, the PAStringList implements a browser delegate method so that it can easily display itself if set to be a browser delegate. Copyright 1992, Jeff Martin (jmartin@next.com) (415) 780-3833. ******************************************************************************/ #define FILE_FROM_PATH(a) ((a)? rindex((a),'/') ? rindex((a),'/')+1 : 0L : (a)) #define EXT_FROM_PATH(a) ( a ? rindex(a,'.') ? rindex(a,'.')+1 : 0L : 0L) #define EXT_STRIP(a) (a ? rindex(a,'.') ? (*rindex(a,'.') = '\0') : 0L :0L) @implementation PAStringList : Storage - init { // Customize Storage to be 'char *' only [super initCount:0 elementSize:4 description:"*"]; // StringList is assumed to be sorted by default and when emptied // Adding a string without sorted flag causes 'isSorted' to be set to NO isSorted = YES; return self; } /****************************************************************************** addString:ifAbsent:noCopy:sorted:at: This method provides a flexible method for adding strings to the list. The ifAbsent flag specifies whether a string should be added if it already exists. The noCopy flag specifies whether the given string should be used (or copied). The sorted flag specifies whether the string should be added alphabetically. The at value specifies where the string should be inserted at if it is not added alphabetically. ******************************************************************************/ - addString:(const char *)string ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at { BOOL stringExists; int index = (ifAbsent || sorted)? [self indexOfString:string exists:&stringExists] : 0; // If we add only if absent and string is in list return self if(ifAbsent && stringExists) return self; // If not noCopy (in other words, if copy) make copy if(!noCopy) string = NXCopyStringBufferFromZone(string, [self zone]); // If sorted, get index else add at 'at'; otherwise set isSorted flag to NO if(sorted) at = index; else isSorted = NO; // Add the string and return [self insertElement:(char **)&string at:at]; return self; } /****************************************************************************** addString... The following methods are convenience methods and provide shorter names for cleaner use assuming the defaults of ifAbsent=NO, noCopy=NO, sorted=NO and at=count. ******************************************************************************/ - addString:(const char *)string { return [self addString:string ifAbsent:NO noCopy:NO sorted:NO at:[self count]]; } - addStringIfAbsent:(const char *)string; { return [self addString:string ifAbsent:YES noCopy:NO sorted:NO at:[self count]]; } - addStringNoCopy:(const char *)string; { return [self addString:string ifAbsent:NO noCopy:YES sorted:NO at:[self count]]; } - addStringIfAbsentNoCopy:(const char *)string; { return [self addString:string ifAbsent:YES noCopy:YES sorted:NO at:[self count]]; } - addString:(const char *)string at:(int)at; { return [self addString:string ifAbsent:NO noCopy:NO sorted:NO at:at]; } - addStringIfAbsent:(const char *)string at:(int)at; { return [self addString:string ifAbsent:YES noCopy:NO sorted:NO at:at]; } - addStringNoCopy:(const char *)string at:(int)at; { return [self addString:string ifAbsent:NO noCopy:YES sorted:NO at:at]; } - addStringIfAbsentNoCopy:(const char *)string at:(int)at; { return [self addString:string ifAbsent:YES noCopy:YES sorted:NO at:at]; } - addStringSorted:(const char *)string; { return [self addString:string ifAbsent:NO noCopy:NO sorted:YES at:0]; } - addStringIfAbsentSorted:(const char *)string; { return [self addString:string ifAbsent:YES noCopy:NO sorted:YES at:0]; } - addStringNoCopySorted:(const char *)string; { return [self addString:string ifAbsent:NO noCopy:YES sorted:YES at:0]; } - addStringIfAbsentNoCopySorted:(const char *)string; { return [self addString:string ifAbsent:YES noCopy:YES sorted:YES at:0]; } /****************************************************************************** addStrings and addPAStringList These methods allow for lists of strings to be added either from a char ** or from a PAStringList object. ******************************************************************************/ - addStrings:(const char *const*)strings ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at { char **temp = (char **)strings; // Add each string individually, incrementing 'at' to preserve their order while(*temp) { [self addString:*temp ifAbsent:ifAbsent noCopy:noCopy sorted:sorted at:at]; at++; temp++; } // If 'noCopy' then we own the memory 'strings' and should free it if(noCopy) free((char *)strings); return self; } - addPAStringList:stringListObject ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at { return [self addStrings:[stringListObject strings] ifAbsent:ifAbsent noCopy:noCopy sorted:sorted at:at]; } /****************************************************************************** addFilename, addFilenames, addFilenamesFromDirectory These methods allow for lists of strings to be added as filenames (with or without their full paths and extensions). addFilenamesFromDirectory actually looks out in the directory specified for files with the given extension (NULL for all files) and adds them to the list. addFilenamesFromStandardLibrariesDirectory goes a step farther and looks in /NextLibary, /LocalLibrary and ~/Libary for the given directory. This is a convenience for the cases where an app searches the standard libraries. ******************************************************************************/ - addFilename:(char *)filename stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at { char newFilename[MAXPATHLEN+1]; // Get filename with appropriate stripping sprintf(newFilename, "%s", stripPath? FILE_FROM_PATH(filename) : filename); if(stripExt) EXT_STRIP(newFilename); // If string was sent no copy, then free it (we have our own) if(noCopy) free(filename); // Add new string with noCopy return [self addString:newFilename ifAbsent:ifAbsent noCopy:NO sorted:sorted at:at]; } - addFilenames:(char **)filenames stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at { char **temp = filenames; // Add each string individually, incrementing 'at' to preserve their order while(*temp) { [self addFilename:*temp stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent noCopy:noCopy sorted:sorted at:at]; at++; temp++; } // If 'noCopy' then we own the memory 'filenames' and should free it if(noCopy) free(filenames); return self; } - addFilenamesFromDirectory:(const char *)dir withExt:(const char *)ext stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted at:(int)at { DIR *thedir = opendir(dir); char tempName[MAXPATHLEN+1]; struct direct *entry; // Look at each entry in the dir. If they aren't hidden (start with '.') // and have the same type (or if there is no type) then add them to list. while(thedir && (entry=readdir(thedir))) if((*entry->d_name != '.') && (!ext||( EXT_FROM_PATH(entry->d_name) && !strcmp(ext, EXT_FROM_PATH(entry->d_name))))) { sprintf(tempName, "%s/%s", dir, entry->d_name); [self addFilename:tempName stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent noCopy:NO sorted:sorted at:at++]; } // Close the directory, store the file counter and return the files. if(thedir) closedir(thedir); return self; } - addFilenamesFromStandardLibrariesDirectory:(const char *)dir withExt:(const char *)ext stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted at:(int)at { char temp[MAXPATHLEN+1]; int oldCount = [self count]; // Add files from NextLibrary sprintf(temp, "/NextLibrary/%s", dir); [self addFilenamesFromDirectory:temp withExt:ext stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent sorted:sorted at:at]; // Increment at to preserve order of /NextLibrary,/LocalLibrary,~/Library at += [self count] - oldCount; oldCount = [self count]; // Add files from LocalLibrary sprintf(temp, "/LocalLibrary/%s", dir); [self addFilenamesFromDirectory:temp withExt:ext stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent sorted:sorted at:at]; // Increment at to preserve order of /NextLibrary,/LocalLibrary,~/Library at += [self count] - oldCount; oldCount = [self count]; // Add files from ~/Library sprintf(temp, "%s/Library/%s", NXHomeDirectory(), dir); [self addFilenamesFromDirectory:temp withExt:ext stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent sorted:sorted at:at]; return self; } /****************************************************************************** addDelimitedStrings... This method takes a delimited string (like the ones passed from the workspace) and searches for the given delimiters (NULL for general whitespace). It adds each string that it finds between the delimiters using the addFilename protocol. ******************************************************************************/ - addDelimitedStrings:(char *)string delimiters:(char *)dels { return [self addDelimitedStrings:string delimiters:dels stripPath:NO stripExt:NO ifAbsent:NO noCopy:NO sorted:NO at:[self count]]; } - addDelimitedStrings:(char *)string delimiters:(char *)dels stripPath:(BOOL)stripPath stripExt:(BOOL)stripExt ifAbsent:(BOOL)ifAbsent noCopy:noCopy sorted:(BOOL)sorted at:(int)at { char defaultDels[] = {' ', '\t', '\n', '\r', '\0'}; int delCount, i; char *currChar, *currString, *stringEnd; // Check to see if we were handed a bogus string if(!string || !strlen(string)) { if(!noCopy) free(string); return self; } // Check for default delimiter and get number of delimiters if(!dels) dels = defaultDels; delCount = strlen(dels) + 1; // Make copy of the string if it is noCopy(going to use string as scratch) if(!noCopy) string = NXCopyStringBuffer(string); currChar = currString = string; stringEnd = (char *)((int)string+strlen(string)); // Look at each character until we pass null terminator(stringEnd) while(currChar <= stringEnd) { // Check the character against the delimiters for(i=0; i<delCount; i++) // If current character matches a delimiter add current string if(*currChar==dels[i]) { // Set delimiter to NULL('\0') *currChar = '\0'; // Only add if there is something to add. Increment at. if(strlen(currString)) [self addFilename:currString stripPath:stripPath stripExt:stripExt ifAbsent:ifAbsent noCopy:NO sorted:sorted at:at++]; // set currString to start of next string(just past curr del) currString = currChar + 1; break; } currChar++; } free(string); return self; } /****************************************************************************** These methods return the strings, and the string at a particular index. ******************************************************************************/ - (const char *const*)strings { return (const char *const*)dataPtr; } - (const char *)stringAt:(int)at { return *(char **)[self elementAt:at]; } /****************************************************************************** stringExists returns whether or not the string is already in the list. indexOfString returns either the strings current index or the index that it should be alphabetically. This is only really useful if the list is sorted. indexOfString:stringExists: is a composite method that returns both values in about the same amount of time that it takes to compute one. ******************************************************************************/ - (BOOL)stringExists:(const char *)string { BOOL exists; [self indexOfString:string exists:&exists]; return exists; } - (unsigned)indexOfString:(const char *)string { return [self indexOfString:string exists:NULL]; } - (unsigned)indexOfString:(const char *)string exists:(BOOL *)exists { int index; // Assume the string won't be found if(exists) *exists = NO; // If list is empty or no string or zero strlen, return end of list if(![self count] || !string || !strlen(string)) return [self count]; // If not sorted do sequential search if(!isSorted) { int i=0; for(i=0; i<[self count]; i++) if(!strcasecmp(string, [self stringAt:i])) { if(exists) *exists = YES; break; } else i++; index = i; } // Otherwise if it is sorted do a binary search else { int l = 0; // lower index int u = [self count] - 1; // upper index int m = 0; // middle index int guess = 0; // compare val. while(l <= u) { m = (l+u)/2; guess = strcasecmp([self stringAt:m], string); // If guess is too high, adjust the upper value if(guess>0) u = m-1; // If guess is too low, adjust the lower value else if(guess<0) l = m+1; // If guess is equal to string, set 'exists' flag and break else { if(exists) *exists = YES; break; } } // If last guess was right or too high index is m; if too low then m+1; if(guess>=0) index = m; else index = m+1; } return index; } /****************************************************************************** These methods allow for the removal of strings by value or index. ******************************************************************************/ - removeString:(const char *)string { BOOL exists; int index = [self indexOfString:string exists:&exists]; if(exists) [self removeStringAt:index]; return self; } - removeStrings:(const char *const*)strings { int i; for(i=0; i<[self count]; i++) [self removeString:strings[i]]; return self; } - (char *)removeStringAt:(int)at { char *string = (char *)[self stringAt:at]; [self removeElementAt:at]; if(![self count]) isSorted = YES; return string; } /****************************************************************************** isSorted returns whether or not the list is currently considered to be sorted. Lists are set to be sorted by default and when empty. isSorted is set to NO when a string is added without the 'sorted' flag. sortStrings: sorts the stringList (ignoring case) and sets the sorted flag. ******************************************************************************/ - (BOOL)isSorted { return isSorted; } // Wrap around strcasecmp to accept 'char **' and NULL strings(NULLs to back) static int strPtrCaseCmp(const void *s1, const void *s2) { if(*(char **)s1==*(char **)s2) return 0; else if(!*(char **)s1) return 1; else if(!*(char **)s2) return -1; else return strcasecmp(*((char **)s1),*((char **)s2)); } - sortStrings:sender { qsort(dataPtr, [self count], sizeof(char *), strPtrCaseCmp); isSorted = YES; return self; } /****************************************************************************** Write and read the PAStringList for archiving. ******************************************************************************/ - write:(NXTypedStream *)stream { BOOL hasPreloadInfo = preloadInfo? YES : NO; [super write:stream]; NXWriteType(stream, "c", &isSorted); NXWriteType(stream, "c", &hasPreloadInfo); if(hasPreloadInfo) { NXWriteType(stream, "*", &preloadInfo->plLibs); NXWriteType(stream, "*", &preloadInfo->plDirs); NXWriteType(stream, "*", &preloadInfo->plExts); NXWriteType(stream, "c", &preloadInfo->plFromAppDir); NXWriteType(stream, "c", &preloadInfo->plFromBundleDir); NXWriteType(stream, "c", &preloadInfo->plStripPath); NXWriteType(stream, "c", &preloadInfo->plStripExt); NXWriteType(stream, "c", &preloadInfo->plIfAbsent); NXWriteType(stream, "c", &preloadInfo->plSorted); } return self; } - read:(NXTypedStream *)stream { BOOL hasPreloadInfo; [super read:stream]; NXReadType(stream, "c", &isSorted); NXReadType(stream, "c", &hasPreloadInfo); if(hasPreloadInfo) { preloadInfo = (PreloadInfo *)calloc(1, sizeof(PreloadInfo)); NXReadType(stream, "*", &preloadInfo->plLibs); NXReadType(stream, "*", &preloadInfo->plDirs); NXReadType(stream, "*", &preloadInfo->plExts); NXReadType(stream, "c", &preloadInfo->plFromAppDir); NXReadType(stream, "c", &preloadInfo->plFromBundleDir); NXReadType(stream, "c", &preloadInfo->plStripPath); NXReadType(stream, "c", &preloadInfo->plStripExt); NXReadType(stream, "c", &preloadInfo->plIfAbsent); NXReadType(stream, "c", &preloadInfo->plSorted); [self addPreLoadLibraryAndDirectoryFilenames]; } return self; } /****************************************************************************** These methods empty the list, free the strings and free the list, respectively. ******************************************************************************/ - empty { isSorted = YES; return [super empty]; } - freeStrings { while([self count]) free([self removeStringAt:0]); return self; } - free { if(preloadInfo) { free(preloadInfo->plLibs); free(preloadInfo->plDirs); free(preloadInfo->plExts); free(preloadInfo); } return [super free]; } /****************************************************************************** This method is provided as a delegate method for browser to quickly display string list. ******************************************************************************/ - (int)browser:sender fillMatrix:matrix inColumn:(int)column { int i; id cellList, theCell; // Set matrix to have the right number of cells. [matrix renewRows:[self count] cols:1]; // Get list of cells from the matrix. cellList = [matrix cellList]; // For each cell set its value, set whether it is a leaf or not and // mark it loaded. for(i=0;i<[cellList count];i++) { theCell = [cellList objectAt:i]; [theCell setStringValue:[self stringAt:i]]; [theCell setLeaf:YES]; [theCell setLoaded:YES]; } // Return the number of rows. return [self count]; } // Interface Builder support - (const char *)getInspectorClassName { return "PAStringListInspector"; } @end /****************************************************************************** These methods set search paths, extensions and flags to preload the string list when unarchived. This allows for an InterfaceBuilder palette to set the StringList to load interesting things when unarchived (ie, "get all of the files of type 'font' from the standard library directory 'Fonts' "). ******************************************************************************/ @implementation PAStringList(Preloading) // Returns the preloadInfo structure, allocating it if neccessary. - (PreloadInfo *)preloadInfo { if(!preloadInfo) preloadInfo =(PreloadInfo *)calloc(1,sizeof(PreloadInfo)); return preloadInfo; } // Return and set the Libraries for preload - (const char *)preLoadLibraries { return preloadInfo ? preloadInfo->plLibs : NULL; } - setPreLoadLibraries:(const char *)libs { free([self preloadInfo]->plLibs); preloadInfo->plLibs = (libs && strlen(libs))? NXCopyStringBufferFromZone(libs, [self zone]) : NULL; return self; } // Return and set the Directories for preload - (const char *)preLoadDirectories { return preloadInfo ? preloadInfo->plDirs : NULL; } - setPreLoadDirectories:(const char *)dirs { free([self preloadInfo]->plDirs); preloadInfo->plDirs = (dirs && strlen(dirs))? NXCopyStringBufferFromZone(dirs, [self zone]) : NULL; return self; } // Return and set the Extensions for preload - (const char *)preLoadExtensions { return preloadInfo ? preloadInfo->plExts : NULL; } - setPreLoadExtensions:(const char *)exts { free([self preloadInfo]->plExts); preloadInfo->plExts = (exts && strlen(exts))? NXCopyStringBufferFromZone(exts, [self zone]) : NULL; return self; } // Return and set whether or not to load from application dir - (BOOL)preLoadFromAppDir { return preloadInfo ? preloadInfo->plFromAppDir : NO; } - setPreLoadFromAppDir:(BOOL)flag { [self preloadInfo]->plFromAppDir = flag; return self; } // Return and set whether or not to load from bundle dir - (BOOL)preLoadFromBundleDir { return preloadInfo ? preloadInfo->plFromBundleDir : NO; } - setPreLoadFromBundleDir:(BOOL)flag { [self preloadInfo]->plFromBundleDir = flag; return self; } // Return and set whether or not to strip paths from preloaded files - (BOOL)preLoadStripPath { return preloadInfo ? preloadInfo->plStripPath : NO;} - setPreLoadStripPath:(BOOL)flag { [self preloadInfo]->plStripPath = flag; return self; } // Return and set whether or not to strip extensions from preloaded files - (BOOL)preLoadStripExt { return preloadInfo ? preloadInfo->plStripExt : NO; } - setPreLoadStripExt:(BOOL)flag { [self preloadInfo]->plStripExt = flag; return self; } // Return and set whether or not to 'add preloaded files only if absent' - (BOOL)preLoadIfAbsent { return preloadInfo ? preloadInfo->plIfAbsent : NO; } - setPreLoadIfAbsent:(BOOL)flag { [self preloadInfo]->plIfAbsent = flag; return self; } // Return and set whether or not to sort preloaded files - (BOOL)preLoadSorted { return preloadInfo ? preloadInfo->plSorted : NO; } - setPreLoadSorted:(BOOL)flag { [self preloadInfo]->plSorted = flag; return self; } // Does the work of preloading. Should only be called from read: - addPreLoadLibraryAndDirectoryFilenames { char dels[] = {':', ',', '.', ' ', '\t', '\n', '\r', '\0'}; id libList = [[[PAStringList alloc] init] addDelimitedStrings:preloadInfo->plLibs delimiters:dels]; id dirList = [[[PAStringList alloc] init] addDelimitedStrings:preloadInfo->plDirs delimiters:dels]; id extList = [[[PAStringList alloc] init] addDelimitedStrings:preloadInfo->plExts delimiters:dels]; int i,j; BOOL plFromAppDir, plFromBundleDir; BOOL plStripPath, plStripExt, plIfAbsent, plSorted; // If preloadInfo doesn't exist return if(!preloadInfo) return self; // Shorten all of the names plFromAppDir = preloadInfo->plFromAppDir; plFromBundleDir = preloadInfo->plFromBundleDir; plStripPath = preloadInfo->plStripPath; plStripExt = preloadInfo->plStripExt; plIfAbsent = preloadInfo->plIfAbsent; plSorted = preloadInfo->plSorted; // Load all of the libraries with given extensions for(i=0; i<[libList count]; i++) if([extList count]) for(j=0; j<[extList count]; j++) [self addFilenamesFromStandardLibrariesDirectory: [libList stringAt:i] withExt:[extList stringAt:j] stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; else [self addFilenamesFromStandardLibrariesDirectory: [libList stringAt:i] withExt:NULL stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; // Load all of the directories with given extensions for(i=0; i<[dirList count]; i++) { char temp[MAXPATHLEN+1]; PAExpandFilename(strcpy(temp, [dirList stringAt:i])); if([extList count]) for(j=0; j<[extList count]; j++) [self addFilenamesFromDirectory:temp withExt:[extList stringAt:j] stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; else [self addFilenamesFromDirectory:temp withExt:NULL stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; } // Load from App directory if need be if(plFromAppDir) { if([extList count]) for(j=0; j<[extList count]; j++) [self addFilenamesFromDirectory: [[NXBundle mainBundle] directory] withExt:[extList stringAt:j] stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; else [self addFilenamesFromDirectory:[[NXBundle mainBundle] directory] withExt:NULL stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; } // Load from Bundle directory if need be if(plFromBundleDir) { if([extList count]) for(j=0; j<[extList count]; j++) [self addFilenamesFromDirectory: [[NXBundle bundleForClass:[self class]] directory] withExt:[extList stringAt:j] stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; else [self addFilenamesFromDirectory: [[NXBundle bundleForClass:[self class]] directory] withExt:NULL stripPath:plStripPath stripExt:plStripExt ifAbsent:plIfAbsent sorted:plSorted at:[self count]]; } // Free string lists [libList freeStrings]; [libList free]; [dirList freeStrings]; [dirList free]; [extList freeStrings]; [extList free]; return self; } @end char *PAExpandFilename(char *fn) { char oldFilename[MAXPATHLEN+1], *fn2 = fn+1; if(*fn=='~') { if(fn[1]=='/') fn2++; strcpy(oldFilename, fn2); sprintf(fn, "%s/%s", NXHomeDirectory(), oldFilename); } return fn; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.