This is MORegexTextCell.m in view mode; [Download] [Up]
// MORegexTextCell.m // // by Mike Ferris // Part of MOKit // Copyright 1993, all rights reserved. // ABOUT MOKit // by Mike Ferris (mike@lorax.com) // // MOKit is a collection of useful and general objects. Permission is // granted by the author to use MOKit in your own programs in any way // you see fit. All other rights pertaining to the kit are reserved by the // author including the right to sell these objects as objects, as part // of a LIBRARY, or as SOURCE CODE. In plain English, I wish to retain // rights to these objects as objects, but allow the use of the objects // as pieces in a fully functional program. Permission is also granted to // redistribute the source code of MOKit for FREE as long as this copyright // notice is left intact and unchanged. NO WARRANTY is expressed or implied. // The author will under no circumstances be held responsible for ANY // consequences from the use of these objects. Since you don't have to pay // for them, and full source is provided, I think this is perfectly fair. #import "MOKit/MORegexTextCell.h" #import "MOKit/MOString.h" #import "regexp.cinc" #import "regsub.cinc" #import <objc/objc-runtime.h> #define CLASS_VERSION 0 #define CLASS_NAME "MORegexTextCell" #define BUNDLE_TYPE "bundle" #define MOSTRING_CLASS_NAME "MOString" @interface MORegexTextCell(Private) + (Class)MO_loadClassBundle:(const char *)className; @end @implementation MORegexTextCell static id MOStringClass; + (Class)MO_loadClassBundle:(const char *)className // Finds the bundle of the same name as the class, grabs it and loads the // class from it and returns the named class. { char pathBuff[MAXPATHLEN+1]; id classBundle = nil; Class class = nil; // Load the bundle if ((class = objc_lookUpClass(className)) == nil) { // class is not already loaded... load it. // Look for the bundle in the main bundle first, // else try in this class's bundle. if (![[NXBundle mainBundle] getPath:pathBuff forResource:className ofType:BUNDLE_TYPE]) { if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff forResource:className ofType:BUNDLE_TYPE]) { NXLogError("[%s loadClassBundle] failed to " "find %s class bundle.", [self name], className); return nil; } } classBundle = [[NXBundle allocFromZone:[self zone]] initForDirectory:pathBuff]; if (!classBundle) { NXLogError("[%s loadClassBundle] failed to " "create bundle for class %s.", [self name], className); return nil; } if ((class = [classBundle classNamed:className]) == nil) { NXLogError("[%s loadClassBundle] failed to " "load %s class from bundle.", [self name], className); return nil; } } return class; } + initialize // Set the version. { if (self == objc_lookUpClass(CLASS_NAME)) { [self setVersion:CLASS_VERSION]; // Load the string class if necessary MOStringClass = [self MO_loadClassBundle:MOSTRING_CLASS_NAME]; } return self; } + (BOOL)isValidRegex:(const char *)str { regexp *re; re = regcomp((char *)str); if (re) { free(re); return YES; } else { return NO; } } - initTextCell:(const char *)aString // Super's DI. Overriden to call our DI. { return [self initTextCell:aString withRegex:NULL]; } - initTextCell:(const char *)aString withRegex:(const char *)re // Our designated initializer. { int i; [super initTextCell:aString]; pieceCount = -1; for (i=0;i<NSUBEXP;i++) { pieces[i]=NULL; } regexStrs = [[List allocFromZone:[self zone]] init]; matchedIndex = -1; allowEmptyString = YES; delegate = nil; if ((re) && (*re)) { if (![self addRegexStr:re]) { #ifdef DEBUG NXLogError("%s debug: initialized with " "incompatible string and pattern.\n", [[self class] name]); #endif /* DEBUG */ } } return self; } - MO_copyRegexStrs:list zone:(NXZone *)zone { int i, c; id string; regexStrs = [[List allocFromZone:zone] init]; c = [list count]; for (i=0; i<c; i++) { string = [[MOStringClass allocFromZone:zone] initStringValue:[[list objectAt:i] stringValue]]; [regexStrs addObject:string]; } return self; } - MO_resetPieces { int i; for (i=0; i<NSUBEXP; i++) { pieces[i]=NULL; } [self isEntryAcceptable:[self stringValue]]; return self; } - copyFromZone:(NXZone *)zone { id obj = [super copyFromZone:zone]; [obj MO_copyRegexStrs:regexStrs zone:zone]; [obj MO_resetPieces]; return obj; } - free { int i; for (i=0;i<NSUBEXP;i++) { if (pieces[i]) NX_FREE(pieces[i]); } [regexStrs freeObjects]; [regexStrs free]; return [super free]; } - (BOOL)isEntryAcceptable:(const char *)aString // Returns YES if the given string matches at least one of the cell's pattern // strings. If a match is found, then the pieces array is filled with the // substrings of aString which matched parenthesized parts of the regular // expression that was matched if any. The pieces methods will only return // valid values if the last call to this method returned YES. { int i, j, c = [regexStrs count]; regexp *re = NULL; matchedIndex = -1; pieceCount = -1; if ((allowEmptyString) && ((aString==NULL) || (*aString=='\0'))) { return YES; } // If we don't have any pattern strings we match anything. if (c==0) { if ((delegate) && ([delegate respondsTo:@selector(finishValidating: for:)])) { return [delegate finishValidating:aString for:self]; } return YES; } // Cycle through the patterns for this cell. Check one by one // starting from the first stopping when one is found which matches // the string. If one is found, populate the pieces array with all // the subexpression matches for the matched pattern. for (i=0; i<c; i++) { // Compile the expression. A bad expression causes immediate // failure. No further patterns are checked. re = regcomp((char *)[[regexStrs objectAt:i] stringValue]); if (!re) { NXBeep(); #ifdef DEBUG NXLogError("%s debug: Regular expression " "pattern #%d: '%s' failed to compile.\n", [[self class] name], i, [[regexStrs objectAt:i] stringValue]); #endif /* DEBUG */ return NO; } // The expression compiled, now check it against the string. if (regexec(re, (char *)aString)==0) { // Failure, check the next one. NX_FREE(re); continue; } // The expression checked, now fill in the pieces array. // First empty it. for (j=0; j<NSUBEXP; j++) { if (pieces[j]) NX_FREE(pieces[j]); pieces[j]=NULL; } // Now fill the array and count how many sub-expressions there were. pieceCount=0; for (j = 0; j < NSUBEXP; j++) { if (re->startp[j] != NULL && re->endp[j] != NULL) { char subStr[5]; NX_MALLOC(pieces[pieceCount], char, strlen(aString)+1); sprintf(subStr, "\\%d", j); regsub(re, subStr, pieces[pieceCount]); pieceCount++; } } NX_FREE(re); matchedIndex = i; if ((delegate) && ([delegate respondsTo:@selector(finishValidating: for:)])) { return [delegate finishValidating:aString for:self]; } return YES; } return NO; } - (const char *)regexStr // returns the first regex string. This method is a convenient shorthand // for regexStrAt:0. If your cell has more than 1 string it is advisable // to use the long form for clarity. { return [self regexStrAt:0]; } - setRegexStr:(const char *)re // For cells that will only have one string, this method will set it. // It frees all the strings that might be there, then adds one for the // given expression. { if ((re) && (*re)) { [regexStrs freeObjects]; [self addRegexStr:re]; return self; } else { return nil; } } - (const char *)regexStrAt:(int)index // Returns regex string number index for this cell. Returns NULL if index // is out of range. { if ((index>=0) && ([regexStrs count]>index)) { return [[regexStrs objectAt:index] stringValue]; } else { return NULL; } } - addRegexStr:(const char *)re // If re is a valid non-empty char pointer a string object is allocated for it // and it is added to the list of patterns for this cell. { id newStr; if ((re) && (*re)) { newStr = [[MOStringClass allocFromZone:[self zone]] initStringValue:re]; [regexStrs addObject:newStr]; if ([self isEntryAcceptable:[self stringValue]]) { return self; } else { return nil; } } else { return nil; } } - removeRegexStrAt:(int)index // removes the string at index from the patterns for this cell. { [[regexStrs removeObjectAt:index] free]; return self; } - (int)regexStrCount // Returns the number of pattern strings for this cell. { return [regexStrs count]; } - regexStrList // returns the list object used to store the pattern strings for this cell. { return regexStrs; } // These routines return the number of () sets in the last regular expression // matched or the actual pattern matched for a given set of () in the last // expression that was matched. - (int)pieceCount { return pieceCount; } - (const char *)pieceAt:(int)n { if ((n >= 0) && (n < pieceCount)) { return pieces[n]; } return NULL; } - (int)matchedPatternIndex // Returns the index into regexStrs of the pattern that was matched with the // last call to isEntryAcceptable or -1 if no match was found. // Note that matchedIndex does not test for whether the string was valid // since it will be -1 if empty strings are allowed and the cell is empty. { return matchedIndex; } - (BOOL)doesAllowEmptyString // Returns YES if this cell will allow empty strings regardless of whether // one of its patterns matches the empty string. NO otherwise. { return allowEmptyString; } - setAllowEmptyString:(BOOL)flag; // If flag is YES this cell will allow empty strings regardless of whether // one of its patterns matches the empty string. If its NO it won't. { allowEmptyString=flag; return self; } - delegate { return delegate; } - setDelegate:obj { delegate = obj; return self; } - setDoubleValue:(double)aDouble { id ret = [super setDoubleValue:aDouble]; [self isEntryAcceptable:[self stringValue]]; return ret; } - setFloatValue:(float)aFloat { id ret = [super setFloatValue:aFloat]; [self isEntryAcceptable:[self stringValue]]; return ret; } - setIntValue:(int)anInt { id ret = [super setIntValue:anInt]; [self isEntryAcceptable:[self stringValue]]; return ret; } - setStringValue:(const char *)aString { id ret = [super setStringValue:aString]; [self isEntryAcceptable:[self stringValue]]; return ret; } - setStringValueNoCopy:(const char *)aString { id ret = [super setStringValueNoCopy:aString]; [self isEntryAcceptable:[self stringValue]]; return ret; } - setStringValueNoCopy:(char *)aString shouldFree:(BOOL)flag { id ret = [super setStringValueNoCopy:aString shouldFree:flag]; [self isEntryAcceptable:[self stringValue]]; return ret; } - awake // Validate the string so the pieces will be right { pieceCount = -1; matchedIndex = -1; if (![self isEntryAcceptable:[self stringValue]]) { #ifdef DEBUG NXLogError("MORegexFormCell debug: awoke with incompatible string and " "pattern.\n"); #endif /* DEBUG */ } return self; } - read:(NXTypedStream *)strm // Read our regular expression pattern string. // int NXTypedStreamClassVersion(NXTypedStream *typedStream, // const char *className) // can be used to handle multi version reading and writing. { int classVersion; [super read:strm]; classVersion = NXTypedStreamClassVersion(strm, CLASS_NAME); switch (classVersion) { case 0: // First version. regexStrs = NXReadObject(strm); NXReadType(strm, "c", &allowEmptyString); delegate = NXReadObject(strm); break; default: NXLogError("[%s read:] class version %d cannot read " "instances archived with version %d", CLASS_NAME, CLASS_VERSION, classVersion); regexStrs = [[List allocFromZone:[self zone]] init]; allowEmptyString = YES; delegate = nil; break; } return self; } - write:(NXTypedStream *)strm // Write our regular expression pattern string. { [super write:strm]; NXWriteObject(strm, regexStrs); NXWriteType(strm, "c", &allowEmptyString); NXWriteObjectReference(strm, delegate); return self; } - (const char *)getInspectorClassName // Return the class name of our inspector. { return "MORegexTextCellInspector"; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.