Sunday, March 13, 2011

NSPredicate: problem solved (almost)

It only took me about four hours, but with big help from this post, I solved the problem of filtering a string containing an array of (possibly invalid) filepaths. And, in the meantime, Dave DeLong himself had responded to my question on StackOverflow from yesterday, which if I'd just waited a bit more, would have saved me some time. Thanks, Dave.

As I mentioned (here), we're just wrapping a filtering routine up in a category on NSString. Here is the first part of the code file including the category:


#import <Foundation/Foundation.h>

@interface NSString (ValidatingFilepathArray)
- (NSNumber *) validate;
@end

@implementation NSString (ValidatingFilepathArray)

- (NSNumber *) validate {
NSArray *A = [self componentsSeparatedByString:@":"];
NSString *s, *p;
NSFileManager *fm = [NSFileManager defaultManager];
for (p in A) {
s = [p stringByExpandingTildeInPath];
if ([fm fileExistsAtPath:s]) {
NSLog(@"passed: %@", p);
}
else {
NSLog(@"failed: %@", p);
return [NSNumber numberWithBool:NO];
}
}
return [NSNumber numberWithBool:YES];
}
@end


In the code at the bottom of the post, we construct a "function expression" from the new NSString method. Two things really confused me. First, in the method


expressionForFunction:selectorName:arguments:


the "function" is the object itself. And the second thing was figuring out the format string for the predicate that wraps up our expression:


@"FUNCTION(SELF, 'validate') isEqual:YES"


This is the output:


> gcc -o test test.m -framework Foundation
> ./test
2011-03-13 16:53:42.974 test[3494:903] passed: ~
2011-03-13 16:53:42.976 test[3494:903] passed: ~/Desktop
2011-03-13 16:53:42.977 test[3494:903] expression for ~:~/Desktop: YES
2011-03-13 16:53:42.978 test[3494:903] failed: xyz
2011-03-13 16:53:42.978 test[3494:903] expression for xyz: NO
-------------------------------------------------
2011-03-13 16:53:42.979 test[3494:903] passed: ~
2011-03-13 16:53:42.980 test[3494:903] passed: ~/Desktop
2011-03-13 16:53:42.980 test[3494:903] failed: xyz
2011-03-13 16:53:42.981 test[3494:903] filtered: ~:~/Desktop
2011-03-13 16:53:42.981 test[3494:903] passed: ~/Desktop
2011-03-13 16:53:42.982 test[3494:903] evaluate: YES


First we construct the expression and evaluate it. In the second part, we construct the predicate and use it to filter an array or just "evaluate" a string. Still to do: test it in an app with bindings and an NSTextField or NSTableView.

Code:


int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *validPathArray = @"~:~/Desktop";
NSExpression *f = [NSExpression expressionForConstantValue:validPathArray];
NSExpression *e = [NSExpression expressionForFunction:f
selectorName:@"validate" arguments:nil];
NSNumber *result = [e expressionValueWithObject:nil context:nil];
NSArray *responses = [NSArray arrayWithObjects:@"NO",@"YES",nil];
NSLog(@"expression for %@: %@", validPathArray,
[responses objectAtIndex:[result intValue]]);

NSString *invalidPathArray = @"xyz";
f = [NSExpression expressionForConstantValue:invalidPathArray];
e = [NSExpression expressionForFunction:f
selectorName:@"validate" arguments:nil];
result = [e expressionValueWithObject:nil context:nil];
NSLog(@"expression for %@: %@", invalidPathArray,
[responses objectAtIndex:[result intValue]]);

printf("-------------------------------------------------\n");

NSPredicate *p;
p = [NSPredicate predicateWithFormat:@"FUNCTION(SELF, 'validate') isEqual:YES"];
NSArray *A = [NSArray arrayWithObjects: validPathArray, invalidPathArray, nil];
NSArray *fA = [A filteredArrayUsingPredicate:p];
for (id obj in fA) {
NSLog(@"filtered: %@", obj);
}
BOOL yesorno = [p evaluateWithObject:@"~/Desktop"];
NSLog(@"evaluate: %@", [responses objectAtIndex:(int) yesorno]);
[pool drain];
return 0;
}