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.