使用Objective-C处置UBB标签

使用Objective-C处理UBB标签

有这么个业务需求,抽象出来如下:

  • 展示一条文案,比如“某某人做了某某事”,这里的“某某人”是一个用户的昵称;
  • 用户可以修改昵称,修改过后再看之前的文案,“某某人”这个昵称需要跟着变化;
  • 文案数据从服务端获取;

根据上述需求,讨论确定了服务端下发的“某某人”采用[UserId]uid[/UserId]这样的UBB标签格式,客户端进行解析和替换。

要解析内容,首先要匹配指定的UBB标签,一种简单粗暴的方案就是先查找第一个[UserId],再查找第一个[/UserId],定位到匹配内容。

为了代码好看点,也可以采用正则表达式匹配。第一个版本如下:

    NSString *reStr = [NSString stringWithFormat:@"[%@].*[/%@]", ubbTag, ubbTag];
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:reStr options:0 error:NULL];

这个版本只能匹配 "UserId]uid[/UserId" ,遗漏了开始和结束的方括号。这时,很久没写正则的我才意识到没有转义。于是有了第二个版本:

    NSString *reStr = [NSString stringWithFormat:@"\[%@].*?[/%@\]", ubbTag, ubbTag];
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:reStr options:0 error:NULL];

继续失败,经过一番捣鼓,有了第三个版本:

    NSString *reStr = [NSString stringWithFormat:@"\\[%@\\].*\\[/%@\\]", ubbTag, ubbTag];
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:reStr options:0 error:NULL];

这样就OK了,能匹配到完整的内容。不过当出现多个[UserId]标签时,会贪婪匹配,所以有了第四个版本,进行懒惰匹配:

    NSString *reStr = [NSString stringWithFormat:@"\\[%@\\].*?\\[/%@\\]", ubbTag, ubbTag];
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:reStr options:0 error:NULL];

当可以准确匹配到UBB标签内容时,就可以对其进行处理了:

#import <Foundation/Foundation.h>

@interface NSMutableString (UBBParser)

- (void)enumUBBTag:(NSString *)ubbTag usingBlock:(void (^)(NSRange tagRange,
                                                           NSString *tagContent,
                                                           NSMutableString *currentStr,
                                                           BOOL *stop))block;

@end

这里基于NSMutableString做了下扩展,参考NSArray的枚举回调接口提供了上述这么一个接口,具体实现可以参考这里的Gist。

    [string enumUBBTag:@"UserId" usingBlock:^(NSRange tagRange, NSString *tagContent, NSMutableString *currentStr, BOOL *stop) {
        NSString *currentDisplayName = ...
        if (currentDisplayName.length == 0) currentDisplayName = tagContent;
        [currentStr replaceCharactersInRange:tagRange withString:currentDisplayName];
    }];

上面是处理的调用。