I wanted a variation of NSRegularExpression's – stringByReplacingMatchesInString:options:range:withTemplate:
method that takes a block instead of a template. The return value of the block would be used as the replacement value. This is more flexible than a template, as you can imagine. Sort of like using the /e
modifier in Perl regular expressions.
So I wrote a category to add the method. This 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 feels a little weird, but it seems to work well. I have a couple of questions about it, though:
- Does this seem like a sane way to implement such a method?
- Is there some way to implement its internals using
-enumerateMatchesInString:options:range:usingBlock:
? I tried it, but could not assign topos
from within the block. But if there was a way to make it work, it'd be cool to also pass the NSMatchingFlags and BOOL and handle them in the same way as that method. Do-able?
Update
Thanks to the answer from Dave DeLong, I've got 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!
Being able to assign to
pos
from within the block would be as simple as changing the declaration from:To:
More info on the
__block
keyword:__block
Variables