开发者

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 NSTextCheckingResults:

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜