How do you use NSRegularExpression's replacementStringForResult:inString:offset:template:
I have a series of characters that I want to match with a regular expression, and replace them with specific strings depending on what they are.
Example:
In => "This is the inp开发者_运维百科ut string where i want to replace 1 2 & 3"
Out => "This is the input string where i want to replace ONE TWO & THREE"
I currently do this by splitting the string using spaces as the separator, and parsing each string individually, incrementally rebuilding the string. I feel this is ugly, and lacking imagination, and kinda slow.
According the Apple documentation I should be able to do this using the replacementStringForResult:inString:offset:template:
method. However I can't seem to understand how to use it correctly.
You can use the method within a for in
loop using matchesInString:options:range:
, which returns an array of matches as NSTextCheckingResult
s:
NSError* error = NULL;
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:@"\\b[1-3]\\b"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSString* yourString = @"This is the input string where i want to replace 1 2 & 3";
NSMutableString* mutableString = [yourString mutableCopy];
NSInteger offset = 0; // keeps track of range changes in the string
// due to replacements.
for (NSTextCheckingResult* result in [regex matchesInString:yourString
options:0
range:NSMakeRange(0, [yourString length])]) {
NSRange resultRange = [result range];
resultRange.location += offset; // resultRange.location is updated
// based on the offset updated below
// implement your own replace functionality using
// replacementStringForResult:inString:offset:template:
// note that in the template $0 is replaced by the match
NSString* match = [regex replacementStringForResult:result
inString:mutableString
offset:offset
template:@"$0"];
NSString* replacement;
if ([match isEqualToString:@"1"]) {
replacement = @"ONE";
} else if ([match isEqualToString:@"2"]) {
replacement = @"TWO";
} else if ([match isEqualToString:@"3"]) {
replacement = @"THREE";
}
// make the replacement
[mutableString replaceCharactersInRange:resultRange withString:replacement];
// update the offset based on the replacement
offset += ([replacement length] - resultRange.length);
}
NSLog(@"mutableString: %@", mutableString); // mutableString: This is the input string where i want to replace ONE TWO & THREE
The answer of Dano works perfectly and following the idea of Pedro in the comments I wrapped the code into a category that takes a block that does the replacement part. This is very handy to use.
NSRegularExpression+Replacement.h
@interface NSRegularExpression (Replacement)
- (NSString *)stringByReplacingMatchesInString:(NSString *)string
options:(NSMatchingOptions)options
range:(NSRange)range
template:(NSString *)templ
withMatchTransformation: (NSString* (^) (NSString* element))transformation;
@end
NSRegularExpression+Replacement.m
@implementation NSRegularExpression (Replacement)
- (NSString *)stringByReplacingMatchesInString:(NSString *)string
options:(NSMatchingOptions)options
range:(NSRange)range
template:(NSString *)templ
withMatchTransformation: (NSString* (^) (NSString* element))transformation
{
NSMutableString* mutableString = [string mutableCopy];
NSInteger offset = 0; // keeps track of range changes in the string due to replacements.
for (NSTextCheckingResult* result in [self matchesInString:string
options:0
range:range])
{
NSRange resultRange = [result range];
resultRange.location += offset; // resultRange.location is updated based on the offset updated below
// implement your own replace functionality using
// replacementStringForResult:inString:offset:template:
// note that in the template $0 is replaced by the match
NSString* match = [self replacementStringForResult:result
inString:mutableString
offset:offset
template:templ];
// get the replacement from the provided block
NSString *replacement = transformation(match);
// make the replacement
[mutableString replaceCharactersInRange:resultRange withString:replacement];
// update the offset based on the replacement
offset += ([replacement length] - resultRange.length);
}
return mutableString;
}
@end
And here is how you use it to solve the initial question:
NSString* yourString = @"This is the input string where i want to replace 1 2 & 3";
NSError* error = nil;
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:@"\\b[1-3]\\b"
options:0
error:&error];
return [regex stringByReplacingMatchesInString:yourString options:0 range:NSMakeRange(0, [yourString length]) template:@"$0" withMatchTransformation:^NSString *(NSString *match) {
NSString* replacement;
if ([match isEqualToString:@"1"]) {
replacement = @"ONE";
} else if ([match isEqualToString:@"2"]) {
replacement = @"TWO";
} else if ([match isEqualToString:@"3"]) {
replacement = @"THREE";
} else {
return replacement;
}
}];
You should use
- (NSString *)stringByReplacingMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)template
or
- (NSUInteger)replaceMatchesInString:(NSMutableString *)string options:(NSMatchingOptions)options range:(NSRange)range withTemplate:(NSString *)template
with string being "This is the input string where i want to replace 1 2 & 3" and template being either "ONE", "TWO" or "THREE".
I needed a more generic solution for the same problem so I refined Dano's answer to a method like this, with a sample usage explained below:
- (NSMutableString *)replaceSubstringsInString:(NSString*)string
usingRegex:(NSString*)searchRegex
withReplacements:(NSDictionary*)replacements {
NSMutableString *newString = [string mutableCopy];
__block NSInteger offset = 0;
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:searchRegex
options:0
error:&error];
[regex enumerateMatchesInString:string
options:0
range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange resultRange = [match range];
resultRange.location += offset;
NSString *substring = [string substringWithRange:match.range];
__block NSString* replacement;
[replacements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([key isEqualToString:substring]) {
replacement = obj;
}
}];
[newString replaceCharactersInRange:resultRange withString:replacement];
offset += ([replacement length] - resultRange.length);
}];
return newString;
}
Usage:
NSString *string = @"This is the input string where i want to replace 1 2 & 3";
NSString *searchRegex = @"\\b[1-3]\\b";
NSDictionary *replacements = @{@"1":@"ONE",@"2":@"TWO",@"3":@"THREE"};
NSMutableString *result = [self replaceSubstringsInString:string
usingRegex:searchRegex
withReplacements:replacements];
Explanation:
You just have to pass in the string
to search for the matching substrings, along with the desired regexSearch
pattern and a replacements
dictionary containing the string pairs with the key
being the substring to be replaced and the object
with the desired replacement string.
Output:
// This is the input string where i want to replace ONE TWO & THREE
I was looking for something similar, but didn't like most of the answers here, so I wrote something inspired by how PHP does string replacement:
@implementation NSString (preg_replace)
- (instancetype)stringByReplacingMatchesFromRegularExpression:(NSRegularExpression *)regularExpression replacementBlock:(NSString * (^)(NSArray *matches))replacementBlock
{
NSMutableString *finalString = self.mutableCopy;
NSUInteger offset = 0;
for (NSTextCheckingResult *match in [regularExpression matchesInString:self options:0 range:NSMakeRange(0, self.length)]) {
NSMutableArray *matches = [NSMutableArray array];
for (NSUInteger index = 0; index < match.numberOfRanges; ++index) {
[matches addObject:[self substringWithRange:[match rangeAtIndex:index]]];
}
NSString *replacementString = replacementBlock(matches.copy);
[finalString replaceCharactersInRange:NSMakeRange(match.range.location + offset, match.range.length) withString:replacementString];
offset += replacementString.length - match.range.length;
}
return finalString;
}
@end
To use it:
NSRegularExpression *expression = [[NSRegularExpression alloc] initWithPattern:@"[0-9A-F]{2}" options:0 error:nil];
NSString *string = @"AB-DE-EF";
NSString *result = [string stringByReplacingMatchesFromRegularExpression:expression replacementBlock:^NSString *(NSArray *matches) {
return [NSString stringWithFormat:@"(%@)", [matches[0] lowercaseString]];
}];
NSLog(@"Result = %@", result); // "(ab)-(de)-(ef)"
I recommend this, much shorter and uses a little memory : Another solution
精彩评论