I need a variant of the NSRegularExpression method – stringByReplacingMatchesInString:options:range:withTemplate: which takes a block instead of a template. The return value of the block will be used as the replacement value. As you can imagine, this is more flexible than a template. Similar to using the /e modifier in Perl regular expressions.
So, I wrote a category to add a method. Here is what I came up with:
@implementation NSRegularExpression (Block) - (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(NSString* (^)(NSTextCheckingResult *result))block { NSMutableString *ret = [NSMutableString string]; NSUInteger pos = 0; for (NSTextCheckingResult *res in [self matchesInString:string options:options range:range]) { if (res.range.location > pos) { [ret appendString:[string substringWithRange:NSMakeRange(pos, res.range.location - pos)]]; } pos = res.range.location + res.range.length; [ret appendString:block(res)]; } if (string.length > pos) { [ret appendString:[string substringFromIndex:pos]]; } return ret; } @end
This is my first attempt to play with blocks in Objective C. It's a bit weird, but it seems to work well. I have a few questions about this:
- Does this seem like a reasonable way to implement such a method?
- Is there a way to implement my internal functions with
-enumerateMatchesInString:options:range:usingBlock: :? I tried, but could not assign pos from inside the block. But if there was a way to make it work, it would be great to pass NSMatchingFlags and BOOL as well and process them the same way as this method. Doable?
Update
Thanks to the answer from Dave DeLong, I have a new version using a block:
@implementation NSRegularExpression (Block) - (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(NSString * (^)(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop))block { NSMutableString *ret = [NSMutableString string]; __block NSUInteger pos = 0; [self enumerateMatchesInString:string options:options range:range usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { if (match.range.location > pos) { [ret appendString:[string substringWithRange:NSMakeRange(pos, match.range.location - pos)]]; } pos = match.range.location + match.range.length; [ret appendString:block(match, flags, stop)]; }]; if (string.length > pos) { [ret appendString:[string substringFromIndex:pos]]; } return [NSString stringWithString:ret]; } @end
Works great, thanks!
source share