神奇的字符串翻译NSDataDetector

前言

我们经常需要对一个字符串内容的类型进行判断,是否是地址,电话或者URL.如果手动去处理,经常容易忽略一些判断规则的细节,导致结果不准确.

系统给我们提供了一个类NSDataDetector,它可以用于判断字符串内部内容的类型,而且,一段字符串中如果同时含有电话,地址,URL等,也可以区分出来,真是神奇的字符串翻译,下面我们就对这个magic class进行详细介绍.

NSDataDetector

NSDataDetectorNSRegularExpression的子类.看一下官方文档的介绍

A specialized regular expression object that matches natural language text for predefined data patterns

NSDataDetector是用于匹配自然语言字符串来确定对象类型的特殊的正则表达式对象.

NSDataDetector用法

目前NSDataDetector类可以用于匹配的类型有:日期, 地址, 链接, 手机号, 物流信息.匹配结果通过返回 NSTextCheckingResult 对象来表述.

NSTextCheckingResult

NSTextCheckingResult是表示匹配结果的类,需要注意的是NSTextCheckingResult可以用来表示NSRegularExpressionNSDataDetector的结果,但是通过NSDataDetector匹配返回的NSTextCheckingResult和父类NSRegularExpression返回的是不同的.

必要属性,存在于所有的解析结果中.

@property (readonly) NSTextCheckingType resultType;
@property (readonly) NSRange range;

可选属性,只有在匹配到特定类型时,某些属性才会有值.

@property (nullable, readonly, copy) NSOrthography *orthography;
@property (nullable, readonly, copy) NSArray *> *grammarDetails;
@property (nullable, readonly, copy) NSDate *date;
@property (nullable, readonly, copy) NSTimeZone *timeZone;
@property (readonly) NSTimeInterval duration;
@property (nullable, readonly, copy) NSDictionary *components API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0));
@property (nullable, readonly, copy) NSURL *URL;
@property (nullable, readonly, copy) NSString *replacementString;
@property (nullable, readonly, copy) NSArray *alternativeStrings API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
@property (nullable, readonly, copy) NSRegularExpression *regularExpression API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0));
@property (nullable, readonly, copy) NSString *phoneNumber API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0));

提到了匹配的类型,我们来看一下NSTextCheckingType resultType都包含哪些类型.

typedef NS_OPTIONS(uint64_t, NSTextCheckingType) {    // a single type
    NSTextCheckingTypeOrthography           = 1ULL << 0,            // language identification
    NSTextCheckingTypeSpelling              = 1ULL << 1,            // spell checking
    NSTextCheckingTypeGrammar               = 1ULL << 2,            // grammar checking
    NSTextCheckingTypeDate                  = 1ULL << 3,            // date/time detection
    NSTextCheckingTypeAddress               = 1ULL << 4,            // address detection
    NSTextCheckingTypeLink                  = 1ULL << 5,            // link detection
    NSTextCheckingTypeQuote                 = 1ULL << 6,            // smart quotes
    NSTextCheckingTypeDash                  = 1ULL << 7,            // smart dashes
    NSTextCheckingTypeReplacement           = 1ULL << 8,            // fixed replacements, such as copyright symbol for (c)
    NSTextCheckingTypeCorrection            = 1ULL << 9,            // autocorrection
    NSTextCheckingTypeRegularExpression API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0))  = 1ULL << 10,           // regular expression matches
    NSTextCheckingTypePhoneNumber API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0))        = 1ULL << 11,           // phone number detection
    NSTextCheckingTypeTransitInformation API_AVAILABLE(macos(10.7), ios(4.0), watchos(2.0), tvos(9.0)) = 1ULL << 12            // transit (e.g. flight) info detection
};

NSDataDetector能匹配的类型日期, 地址, 链接, 手机号, 物流信息.只有

NSTextCheckingTypeDate
NSTextCheckingTypeAddress
NSTextCheckingTypeLink
NSTextCheckingTypePhoneNumber
NSTextCheckingTypeTransitInformation

NSDataDetector返回的结果肯定属于dataDetectorTypes类型的某一种,并且根据结果类型的不同,匹配对应的属性.比如
日期类型NSTextCheckingTypeDate会包含NSDateNSTimeZone类型的timeZoneduration.
链接类型NSTextCheckingTypeLink会包含NSURL等.

示例

以下代码通过NSDataDetector来判断手机号URL,如果失败会返回error.

NSError *error = nil; 
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes: NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber error:&error];

NSDataDetector实例创建后,可以通过NSRegularExpression的方法numberOfMatches(in:options:range:)来获得字符串在某个rang内匹配的个数.

NSUInteger numberOfMatches = [detector numberOfMatchesInString:string options:0 range:NSMakeRange(0, [string length])];

如果只是想获取整个字符串的第一个匹配对象,numberOfMatches(in:options:range:)方法就可以.但是类型判断不同于正则表达式的地方在于,客户端会更注重分析后的附加信息.也就是NSTextCheckingResult对应的可选属性.

result的附加信息取决于其所属的类型.如果是NSTextCheckingTypeLink类型,那么属性URL就是关键的附加信息.如果是NSTextCheckingTypePhoneNumber类型,则phoneNumber是附加信息.

NSDateDetector的两个方法 matches(in:options:range:)firstMatch(in:options:range:)都可以用于获取解析结果.不同的是matches(in:options:range:)会返回所有的匹配结果,而firstMatch(in:options:range:)只会返回第一个匹配结果.

下面的代码段就是获取字符串中所有的链接电话号码的匹配结果.

NSArray *matches = [detector matchesInString:string options:0 range:NSMakeRange(0, [string length])]; 
for (NSTextCheckingResult *match in matches) {
     NSRange matchRange = [match range]; 
     if ([match resultType] == NSTextCheckingTypeLink) { 
        NSURL *url = [match URL]; 
     } else if ([match resultType] == NSTextCheckingTypePhoneNumber) { 
        NSString *phoneNumber = [match phoneNumber];
   } 
}

NSRegularExpression类的enumerator方法是匹配中最常用的方法.该方法运行我们多次去匹配一个字符串,在回调中自由地实现各种定制化需求,并且可以随时控制遍历停止的时机.通过block回调的属性BOOL *stop可以停止遍历.

下面是一个在匹配了特定次数后停止遍历的一个示例,可以用作参考.

__block NSUInteger count = 0;
 [detector enumerateMatchesInString:string 
                                options:0 
                                range:NSMakeRange(0, [string length])    
                                usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { 
       NSRange matchRange = [match range];
       if ([match resultType] == NSTextCheckingTypeLink) { 
            NSURL *url = [match URL];
        } else if ([match resultType] == NSTextCheckingTypePhoneNumber) {
            NSString *phoneNumber = [match phoneNumber]; 
        }
         if (++count >= 100) *stop = YES;
 }];

注意

NSDataDetector只适用于解析特定类型的自然语言.只使用一定的情况匹配,并不是全能的.

如果文本具有特殊的格式,一个使用对应的系统提供的类型转换formatter来处理.比如想解析时间戳类型的数据,就应该使用DateFormatter类来解析,并输出NSDate对象.

如果文本是特定的类型如XMLJSON,那么应该先摘取自然语言,使用
XMLParserJSONSerialization进行解析.

相关文档

NSHipster的NSDataDetector

你可能感兴趣的:(神奇的字符串翻译NSDataDetector)