| // |
| // NBPhoneNumberUtil.m |
| // Band |
| // |
| // Created by NHN Corp. Last Edited by BAND dev team (band_dev@nhn.com) |
| // |
| |
| #import "NBPhoneNumberUtil.h" |
| #import "NBPhoneNumber.h" |
| #import "NBNumberFormat.h" |
| #import "NBPhoneNumberDesc.h" |
| #import "NBPhoneMetaData.h" |
| #import "math.h" |
| |
| #import <CoreTelephony/CTTelephonyNetworkInfo.h> |
| #import <CoreTelephony/CTCarrier.h> |
| |
| |
| #pragma mark - Static Int variables - |
| const static NSUInteger NANPA_COUNTRY_CODE_ = 1; |
| const static NSUInteger MIN_LENGTH_FOR_NSN_ = 2; |
| const static NSUInteger MAX_LENGTH_FOR_NSN_ = 16; |
| const static NSUInteger MAX_LENGTH_COUNTRY_CODE_ = 3; |
| const static NSUInteger MAX_INPUT_STRING_LENGTH_ = 250; |
| |
| |
| #pragma mark - Static String variables - |
| NSString *INVALID_COUNTRY_CODE_STR = @"Invalid country calling code"; |
| NSString *NOT_A_NUMBER_STR = @"The string supplied did not seem to be a phone number"; |
| NSString *TOO_SHORT_AFTER_IDD_STR = @"Phone number too short after IDD"; |
| NSString *TOO_SHORT_NSN_STR = @"The string supplied is too short to be a phone number"; |
| NSString *TOO_LONG_STR = @"The string supplied is too long to be a phone number"; |
| |
| NSString *UNKNOWN_REGION_ = @"ZZ"; |
| NSString *COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX_ = @"3"; |
| NSString *PLUS_SIGN = @"+"; |
| NSString *STAR_SIGN_ = @"*"; |
| NSString *RFC3966_EXTN_PREFIX_ = @";ext="; |
| NSString *RFC3966_PREFIX_ = @"tel:"; |
| NSString *RFC3966_PHONE_CONTEXT_ = @";phone-context="; |
| NSString *RFC3966_ISDN_SUBADDRESS_ = @";isub="; |
| NSString *DEFAULT_EXTN_PREFIX_ = @" ext. "; |
| NSString *VALID_ALPHA_ = @"A-Za-z"; |
| |
| #pragma mark - Static regular expression strings - |
| NSString *NON_DIGITS_PATTERN_ = @"\\D+"; |
| NSString *CC_PATTERN_ = @"\\$CC"; |
| NSString *FIRST_GROUP_PATTERN_ = @"(\\$\\d)"; |
| NSString *FIRST_GROUP_ONLY_PREFIX_PATTERN_ = @"^\\(?\\$1\\)?"; |
| NSString *NP_PATTERN_ = @"\\$NP"; |
| NSString *FG_PATTERN_ = @"\\$FG"; |
| NSString *VALID_ALPHA_PHONE_PATTERN_STRING = @"(?:.*?[A-Za-z]){3}.*"; |
| NSString *UNIQUE_INTERNATIONAL_PREFIX_ = @"[\\d]+(?:[~\\u2053\\u223C\\uFF5E][\\d]+)?"; |
| |
| |
| #pragma mark - NBPhoneNumberUtil interface - |
| @interface NBPhoneNumberUtil () |
| |
| /* |
| Terminologies |
| - Country Number (CN) = Country code for i18n calling |
| - Country Code (CC) : ISO country codes (2 chars) |
| Ref. site (countrycode.org) |
| */ |
| @property (nonatomic, readonly) NSDictionary *coreMetaData; |
| @property (nonatomic, readonly) NSRegularExpression *PLUS_CHARS_PATTERN, *CAPTURING_DIGIT_PATTERN, *VALID_ALPHA_PHONE_PATTERN_; |
| @property (nonatomic, readonly) NSString *LEADING_PLUS_CHARS_PATTERN_, *EXTN_PATTERN_, *SEPARATOR_PATTERN_, *VALID_PHONE_NUMBER_PATTERN_, *VALID_START_CHAR_PATTERN_, *UNWANTED_END_CHAR_PATTERN_, *SECOND_NUMBER_START_PATTERN_; |
| |
| @property (nonatomic, readonly) NSDictionary *ALPHA_MAPPINGS_, *ALL_NORMALIZATION_MAPPINGS_, *DIALLABLE_CHAR_MAPPINGS_, *ALL_PLUS_NUMBER_GROUPING_SYMBOLS_; |
| |
| @property (nonatomic, strong, readwrite) NSMutableDictionary *mapCCode2CN; |
| @property (nonatomic, strong, readwrite) NSMutableDictionary *mapCN2CCode; |
| |
| @property (nonatomic, strong, readwrite) NSMutableDictionary *i18nNumberFormat; |
| @property (nonatomic, strong, readwrite) NSMutableDictionary *i18nPhoneNumberDesc; |
| @property (nonatomic, strong, readwrite) NSMutableDictionary *i18nPhoneMetadata; |
| |
| @end |
| |
| |
| @implementation NBPhoneNumberUtil |
| |
| + (NBPhoneNumberUtil*)sharedInstance |
| { |
| static NBPhoneNumberUtil *sharedOnceInstance = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ sharedOnceInstance = [[self alloc] init]; }); |
| return sharedOnceInstance; |
| } |
| |
| |
| + (NBPhoneNumberUtil*)sharedInstanceForTest |
| { |
| static NBPhoneNumberUtil *sharedOnceInstanceForTest = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ sharedOnceInstanceForTest = [[self alloc] initForTest]; }); |
| return sharedOnceInstanceForTest; |
| } |
| |
| |
| + (NSString*)stringByTrimming:(NSString*)aString |
| { |
| NSString *aRes = @""; |
| NSArray *newlines = [aString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; |
| for (NSString *line in newlines) |
| { |
| NSString *performedString = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| |
| if (performedString.length > 0) |
| aRes = [aRes stringByAppendingString:performedString]; |
| } |
| |
| if (newlines.count <= 0) |
| return aString; |
| |
| return aRes; |
| } |
| |
| |
| #pragma mark - Regular expression Utilities - |
| - (BOOL)hasValue:(NSString*)string |
| { |
| if (string == nil || [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length <= 0) |
| { |
| return NO; |
| } |
| |
| return YES; |
| } |
| |
| |
| - (NSMutableArray*)componentsSeparatedByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| NSString *replacedString = [self replaceStringByRegex:sourceString regex:pattern withTemplate:@"<SEP>"]; |
| NSMutableArray *resArray = [[replacedString componentsSeparatedByString:@"<SEP>"] mutableCopy]; |
| [resArray removeObject:@""]; |
| return resArray; |
| } |
| |
| |
| - (NSInteger)stringPositionByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| if (sourceString == nil || sourceString.length <= 0 || pattern == nil || pattern.length <= 0) { |
| return -1; |
| } |
| |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| |
| NSInteger foundPosition = -1; |
| |
| if (matches.count > 0) |
| { |
| NSTextCheckingResult *match = [matches objectAtIndex:0]; |
| return match.range.location; |
| } |
| |
| return foundPosition; |
| } |
| |
| |
| - (NSInteger)indexOfStringByString:(NSString*)sourceString target:(NSString*)targetString |
| { |
| NSRange finded = [sourceString rangeOfString:targetString]; |
| if (finded.location != NSNotFound) |
| { |
| return finded.location; |
| } |
| |
| return -1; |
| } |
| |
| |
| - (NSString*)replaceFirstStringByRegex:(NSString*)sourceString regex:(NSString*)pattern withTemplate:(NSString*)templateString |
| { |
| NSString *replacementResult = [sourceString copy]; |
| NSError *error = nil; |
| |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSRange replaceRange = [currentPattern rangeOfFirstMatchInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| |
| if (replaceRange.location != NSNotFound) |
| { |
| replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy] options:0 |
| range:replaceRange |
| withTemplate:templateString]; |
| } |
| |
| return replacementResult; |
| } |
| |
| |
| - (NSString*)replaceStringByRegex:(NSString*)sourceString regex:(NSString*)pattern withTemplate:(NSString*)templateString |
| { |
| NSString *replacementResult = [sourceString copy]; |
| NSError *error = nil; |
| |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| |
| if ([matches count] == 1) |
| { |
| NSRange replaceRange = [currentPattern rangeOfFirstMatchInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| |
| if (replaceRange.location != NSNotFound) |
| { |
| replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy] options:0 |
| range:replaceRange |
| withTemplate:templateString]; |
| } |
| return replacementResult; |
| } |
| |
| if ([matches count] > 1) |
| { |
| replacementResult = [currentPattern stringByReplacingMatchesInString:[replacementResult mutableCopy] options:0 |
| range:NSMakeRange(0, sourceString.length) withTemplate:templateString]; |
| return replacementResult; |
| } |
| |
| return replacementResult; |
| } |
| |
| |
| - (NSTextCheckingResult*)matcheFirstByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| if ([matches count] > 0) |
| return [matches objectAtIndex:0]; |
| return nil; |
| } |
| |
| |
| - (NSArray*)matchesByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| return matches; |
| } |
| |
| |
| - (NSArray*)matchedStringByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| NSArray *matches = [self matchesByRegex:sourceString regex:pattern]; |
| NSMutableArray *matchString = [[NSMutableArray alloc] init]; |
| |
| for (NSTextCheckingResult *match in matches) |
| { |
| NSString *curString = [sourceString substringWithRange:match.range]; |
| [matchString addObject:curString]; |
| } |
| |
| return matchString; |
| } |
| |
| |
| - (BOOL)isStartingStringByRegex:(NSString*)sourceString regex:(NSString*)pattern |
| { |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; |
| NSArray *matches = [currentPattern matchesInString:sourceString options:0 range:NSMakeRange(0, sourceString.length)]; |
| |
| for (NSTextCheckingResult *match in matches) |
| { |
| if (match.range.location == 0) |
| { |
| return YES; |
| } |
| } |
| |
| return NO; |
| } |
| |
| |
| - (NSString*)stringByReplacingOccurrencesString:(NSString *)sourceString withMap:(NSDictionary *)dicMap removeNonMatches:(BOOL)bRemove |
| { |
| NSMutableString *targetString = [[NSMutableString alloc] initWithString:@""]; |
| |
| for(NSUInteger i=0; i<sourceString.length; i++) |
| { |
| unichar oneChar = [sourceString characterAtIndex:i]; |
| NSString *keyString = [NSString stringWithCharacters:&oneChar length:1]; |
| NSString *mappedValue = [dicMap objectForKey:keyString]; |
| if (mappedValue != nil) |
| { |
| [targetString appendString:mappedValue]; |
| } |
| else |
| { |
| if (bRemove == NO) |
| { |
| [targetString appendString:keyString]; |
| } |
| } |
| } |
| |
| return targetString; |
| } |
| |
| |
| - (BOOL)isNaN:(NSString*)sourceString |
| { |
| NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet]; |
| NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:sourceString]; |
| BOOL hasNumberOnly = [alphaNums isSupersetOfSet:inStringSet]; |
| |
| return !hasNumberOnly; |
| } |
| |
| /* |
| - (NSString*)numbersOnly:(NSString*)phoneNumber |
| { |
| NSMutableString *strippedString = [NSMutableString stringWithCapacity:phoneNumber.length]; |
| |
| NSScanner *scanner = [NSScanner scannerWithString:phoneNumber]; |
| NSCharacterSet *numbers = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"]; |
| |
| while ([scanner isAtEnd] == NO) { |
| NSString *buffer; |
| if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) { |
| [strippedString appendString:buffer]; |
| |
| } else { |
| [scanner setScanLocation:([scanner scanLocation] + 1)]; |
| } |
| } |
| |
| return strippedString; |
| } |
| */ |
| |
| |
| - (NSString*)getNationalSignificantNumber:(NBPhoneNumber*)phoneNumber |
| { |
| if (phoneNumber.italianLeadingZero) |
| { |
| return [NSString stringWithFormat:@"0%@", phoneNumber.nationalNumber]; |
| } |
| |
| return [NSString stringWithFormat:@"%@", phoneNumber.nationalNumber]; |
| } |
| |
| |
| - (NSArray*)regionCodeFromCountryCode:(NSNumber *)countryCodeNumber |
| { |
| if (self.mapCN2CCode == nil || [self.mapCN2CCode count] <= 0) |
| { |
| return nil; |
| } |
| |
| id res = [self.mapCN2CCode objectForKey:[NSString stringWithFormat:@"%@", countryCodeNumber]]; |
| |
| if (res && [res isKindOfClass:[NSArray class]] && [((NSArray*)res) count] > 0) |
| { |
| return res; |
| } |
| |
| return nil; |
| } |
| |
| |
| - (NSString*)countryCodeFromRegionCode:(NSString*)regionCode |
| { |
| if (self.mapCCode2CN == nil || [self.mapCCode2CN count] <= 0) |
| { |
| return nil; |
| } |
| |
| id res = [self.mapCCode2CN objectForKey:regionCode]; |
| |
| if (res) |
| { |
| return res; |
| } |
| |
| return nil; |
| } |
| |
| |
| #pragma mark - Initializations - |
| - (id)init |
| { |
| self = [super init]; |
| if (self) |
| { |
| [self initRegularExpressionSet]; |
| [self initNormalizationMappings]; |
| |
| NSDictionary *resData = [self loadMetadata:@"NBPhoneNumberMetadata"]; |
| _coreMetaData = [resData objectForKey:@"countryToMetadata"]; |
| _mapCN2CCode = [resData objectForKey:@"countryCodeToRegionCodeMap"]; |
| |
| [self initCC2CN]; |
| } |
| |
| return self; |
| } |
| |
| |
| - (id)initForTest |
| { |
| self = [super init]; |
| if (self) |
| { |
| [self initRegularExpressionSet]; |
| [self initNormalizationMappings]; |
| |
| NSDictionary *resData = [self loadMetadata:@"NBPhoneNumberMetadataForTesting"]; |
| _coreMetaData = [resData objectForKey:@"countryToMetadata"]; |
| _mapCN2CCode = [resData objectForKey:@"countryCodeToRegionCodeMap"]; |
| |
| [self initCC2CN]; |
| } |
| |
| return self; |
| } |
| |
| |
| - (NSDictionary*)loadMetadata:(NSString*)name |
| { |
| NSDictionary *unarchiveData = nil; |
| |
| @try { |
| NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:@"plist"]; |
| NSData *fileData = [NSData dataWithContentsOfFile:filePath]; |
| unarchiveData = [NSKeyedUnarchiver unarchiveObjectWithData:fileData]; |
| } |
| @catch (NSException *exception) { |
| return unarchiveData; |
| } |
| |
| return unarchiveData; |
| } |
| |
| /* |
| - (NSDictionary*)generateMetadata:(id)metaClass |
| { |
| NSMutableDictionary *resMedata = [[NSMutableDictionary alloc] init]; |
| NSDictionary *srcMedata = nil; |
| |
| if ([metaClass isKindOfClass:[NBPhoneNumberMetadataForTesting class]]) |
| { |
| srcMedata = ((NBPhoneNumberMetadataForTesting*)metaClass).metadata; |
| } |
| else if ([metaClass isKindOfClass:[NBPhoneNumberMetadata class]]) |
| { |
| srcMedata = ((NBPhoneNumberMetadata*)metaClass).metadata; |
| } |
| else |
| { |
| return resMedata; |
| } |
| |
| NSDictionary *countryCodeToRegionCodeMap = [srcMedata objectForKey:@"countryCodeToRegionCodeMap"]; |
| NSDictionary *countryToMetadata = [srcMedata objectForKey:@"countryToMetadata"]; |
| NSLog(@"- countryCodeToRegionCodeMap count [%d]", [countryCodeToRegionCodeMap count]); |
| NSLog(@"- countryToMetadata count [%d]", [countryToMetadata count]); |
| |
| NSMutableDictionary *genetatedMetaData = [[NSMutableDictionary alloc] init]; |
| |
| for (id key in [countryToMetadata allKeys]) |
| { |
| id metaData = [countryToMetadata objectForKey:key]; |
| |
| NBPhoneMetaData *newMetaData = [[NBPhoneMetaData alloc] init]; |
| [newMetaData buildData:metaData]; |
| |
| [genetatedMetaData setObject:newMetaData forKey:key]; |
| } |
| |
| [resMedata setObject:countryCodeToRegionCodeMap forKey:@"countryCodeToRegionCodeMap"]; |
| [resMedata setObject:genetatedMetaData forKey:@"countryToMetadata"]; |
| |
| return resMedata; |
| } |
| */ |
| |
| |
| - (void)initRegularExpressionSet |
| { |
| _VALID_PUNCTUATION = @"-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~"; |
| _VALID_DIGITS_STRING = @"0-90-9٠-٩۰-۹"; |
| _PLUS_CHARS_ = @"++"; |
| _REGION_CODE_FOR_NON_GEO_ENTITY = @"001"; |
| |
| NSString *EXTN_PATTERNS_FOR_PARSING_ = @"(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xxX##~~]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)$"; |
| |
| NSError *error = nil; |
| |
| _PLUS_CHARS_PATTERN = |
| [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"[%@]+", _PLUS_CHARS_] options:0 error:&error]; |
| |
| _LEADING_PLUS_CHARS_PATTERN_ = [NSString stringWithFormat:@"^[%@]+", _PLUS_CHARS_]; |
| |
| _CAPTURING_DIGIT_PATTERN = |
| [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"([%@])", _VALID_DIGITS_STRING] options:0 error:&error]; |
| |
| _VALID_START_CHAR_PATTERN_ = [NSString stringWithFormat:@"[%@%@]", _PLUS_CHARS_, _VALID_DIGITS_STRING]; |
| |
| _SECOND_NUMBER_START_PATTERN_ = @"[\\\\\\/] *x"; |
| |
| _VALID_ALPHA_PHONE_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:VALID_ALPHA_PHONE_PATTERN_STRING options:0 error:&error]; |
| |
| _UNWANTED_END_CHAR_PATTERN_ = [NSString stringWithFormat:@"[^%@%@#]+$", _VALID_DIGITS_STRING, VALID_ALPHA_]; |
| |
| _EXTN_PATTERN_ = [NSString stringWithFormat:@"(?:%@)$", EXTN_PATTERNS_FOR_PARSING_]; |
| |
| |
| _SEPARATOR_PATTERN_ = [NSString stringWithFormat:@"[%@]+", _VALID_PUNCTUATION]; |
| |
| _VALID_PHONE_NUMBER_PATTERN_ = @"^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~*]*[0-90-9٠-٩۰-۹]){3,}[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~*A-Za-z0-90-9٠-٩۰-۹]*(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xx##~~]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)?$"; |
| } |
| |
| |
| - (void)dealloc |
| { |
| [self clearCC2CN]; |
| [self clearCN2CC]; |
| } |
| |
| |
| - (void)clearCC2CN |
| { |
| if (self.mapCCode2CN != nil) |
| { |
| [self.mapCCode2CN removeAllObjects]; |
| self.mapCCode2CN = nil; |
| } |
| } |
| |
| |
| - (void)clearCN2CC |
| { |
| if (self.mapCN2CCode != nil) |
| { |
| [self.mapCN2CCode removeAllObjects]; |
| self.mapCN2CCode = nil; |
| } |
| } |
| |
| |
| - (void)initNormalizationMappings |
| { |
| _DIGIT_MAPPINGS = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9", |
| // Fullwidth digit 0 to 9 |
| @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3", @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16", @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19", |
| // Arabic-indic digit 0 to 9 |
| @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3", @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666", @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669", |
| // Eastern-Arabic digit 0 to 9 |
| @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3", @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6", @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9", nil]; |
| |
| _DIALLABLE_CHAR_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9", |
| @"+", @"+", @"*", @"*", nil]; |
| |
| _ALPHA_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"2", @"A", @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F", @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J", |
| @"5", @"K", @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P", @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T", |
| @"8", @"U", @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z", nil]; |
| |
| _ALL_NORMALIZATION_MAPPINGS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9", |
| // Fullwidth digit 0 to 9 |
| @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3", @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16", @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19", |
| // Arabic-indic digit 0 to 9 |
| @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3", @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666", @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669", |
| // Eastern-Arabic digit 0 to 9 |
| @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3", @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6", @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9", |
| @"2", @"A", @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F", @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J", |
| @"5", @"K", @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P", @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T", |
| @"8", @"U", @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z", nil]; |
| |
| _ALL_PLUS_NUMBER_GROUPING_SYMBOLS_ = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4", @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9", |
| @"A", @"A", @"B", @"B", @"C", @"C", @"D", @"D", @"E", @"E", @"F", @"F", @"G", @"G", @"H", @"H", @"I", @"I", @"J", @"J", |
| @"K", @"K", @"L", @"L", @"M", @"M", @"N", @"N", @"O", @"O", @"P", @"P", @"Q", @"Q", @"R", @"R", @"S", @"S", @"T", @"T", |
| @"U", @"U", @"V", @"V", @"W", @"W", @"X", @"X", @"Y", @"Y", @"Z", @"Z", @"A", @"a", @"B", @"b", @"C", @"c", @"D", @"d", |
| @"E", @"e", @"F", @"f", @"G", @"g", @"H", @"h", @"I", @"i", @"J", @"j", @"K", @"k", @"L", @"l", @"M", @"m", @"N", @"n", |
| @"O", @"o", @"P", @"p", @"Q", @"q", @"R", @"r", @"S", @"s", @"T", @"t", @"U", @"u", @"V", @"v", @"W", @"w", @"X", @"x", |
| @"Y", @"y", @"Z", @"z", @"-", @"-", @"-", @"\uFF0D", @"-", @"\u2010", @"-", @"\u2011", @"-", @"\u2012", @"-", @"\u2013", @"-", @"\u2014", @"-", @"\u2015", |
| @"-", @"\u2212", @"/", @"/", @"/", @"\uFF0F", @" ", @" ", @" ", @"\u3000", @" ", @"\u2060", @".", @".", @".", @"\uFF0E", nil]; |
| |
| } |
| |
| |
| - (void)initCC2CN |
| { |
| [self clearCC2CN]; |
| _mapCCode2CN = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @"1", @"US", @"1", @"AG", @"1", @"AI", @"1", @"AS", @"1", @"BB", @"1", @"BM", @"1", @"BS", @"1", @"CA", @"1", @"DM", @"1", @"DO", |
| @"1", @"GD", @"1", @"GU", @"1", @"JM", @"1", @"KN", @"1", @"KY", @"1", @"LC", @"1", @"MP", @"1", @"MS", @"1", @"PR", @"1", @"SX", |
| @"1", @"TC", @"1", @"TT", @"1", @"VC", @"1", @"VG", @"1", @"VI", @"7", @"RU", @"7", @"KZ", |
| @"20", @"EG", @"27", @"ZA", |
| @"30", @"GR", @"31", @"NL", @"32", @"BE", @"33", @"FR", @"34", @"ES", @"36", @"HU", @"39", @"IT", |
| @"40", @"RO", @"41", @"CH", @"43", @"AT", @"44", @"GB", @"44", @"GG", @"44", @"IM", @"44", @"JE", @"45", @"DK", @"46", @"SE", @"47", @"NO", @"47", @"SJ", @"48", @"PL", @"49", @"DE", |
| @"51", @"PE", @"52", @"MX", @"53", @"CU", @"54", @"AR", @"55", @"BR", @"56", @"CL", @"57", @"CO", @"58", @"VE", |
| @"60", @"MY", @"61", @"AU", @"61", @"CC", @"61", @"CX", @"62", @"ID", @"63", @"PH", @"64", @"NZ", @"65", @"SG", @"66", @"TH", |
| @"81", @"JP", @"82", @"KR", @"84", @"VN", @"86", @"CN", |
| @"90", @"TR", @"91", @"IN", @"92", @"PK", @"93", @"AF", @"94", @"LK", @"95", @"MM", @"98", @"IR", |
| @"211", @"SS", @"212", @"MA", @"212", @"EH", @"213", @"DZ", @"216", @"TN", @"218", @"LY", |
| @"220", @"GM", @"221", @"SN", @"222", @"MR", @"223", @"ML", @"224", @"GN", @"225", @"CI", @"226", @"BF", @"227", @"NE", @"228", @"TG", @"229", @"BJ", |
| @"230", @"MU", @"231", @"LR", @"232", @"SL", @"233", @"GH", @"234", @"NG", @"235", @"TD", @"236", @"CF", @"237", @"CM", @"238", @"CV", @"239", @"ST", |
| @"240", @"GQ", @"241", @"GA", @"242", @"CG", @"243", @"CD", @"244", @"AO", @"245", @"GW", @"246", @"IO", @"247", @"AC", @"248", @"SC", @"249", @"SD", |
| @"250", @"RW", @"251", @"ET", @"252", @"SO", @"253", @"DJ", @"254", @"KE", @"255", @"TZ", @"256", @"UG", @"257", @"BI", @"258", @"MZ", |
| @"260", @"ZM", @"261", @"MG", @"262", @"RE", @"262", @"YT", @"263", @"ZW", @"264", @"NA", @"265", @"MW", @"266", @"LS", @"267", @"BW", @"268", @"SZ", @"269", @"KM", |
| @"290", @"SH", @"291", @"ER", @"297", @"AW", @"298", @"FO", @"299", @"GL", |
| @"350", @"GI", @"351", @"PT", @"352", @"LU", @"353", @"IE", @"354", @"IS", @"355", @"AL", @"356", @"MT", @"357", @"CY", @"358", @"FI",@"358", @"AX", @"359", @"BG", |
| @"370", @"LT", @"371", @"LV", @"372", @"EE", @"373", @"MD", @"374", @"AM", @"375", @"BY", @"376", @"AD", @"377", @"MC", @"378", @"SM", @"379", @"VA", |
| @"380", @"UA", @"381", @"RS", @"382", @"ME", @"385", @"HR", @"386", @"SI", @"387", @"BA", @"389", @"MK", |
| @"420", @"CZ", @"421", @"SK", @"423", @"LI", |
| @"500", @"FK", @"501", @"BZ", @"502", @"GT", @"503", @"SV", @"504", @"HN", @"505", @"NI", @"506", @"CR", @"507", @"PA", @"508", @"PM", @"509", @"HT", |
| @"590", @"GP", @"590", @"BL", @"590", @"MF", @"591", @"BO", @"592", @"GY", @"593", @"EC", @"594", @"GF", @"595", @"PY", @"596", @"MQ", @"597", @"SR", @"598", @"UY", @"599", @"CW", @"599", @"BQ", |
| @"670", @"TL", @"672", @"NF", @"673", @"BN", @"674", @"NR", @"675", @"PG", @"676", @"TO", @"677", @"SB", @"678", @"VU", @"679", @"FJ", |
| @"680", @"PW", @"681", @"WF", @"682", @"CK", @"683", @"NU", @"685", @"WS", @"686", @"KI", @"687", @"NC", @"688", @"TV", @"689", @"PF", |
| @"690", @"TK", @"691", @"FM", @"692", @"MH", |
| @"800", @"001", @"808", @"001", |
| @"850", @"KP", @"852", @"HK", @"853", @"MO", @"855", @"KH", @"856", @"LA", |
| @"870", @"001", @"878", @"001", |
| @"880", @"BD", @"881", @"001", @"882", @"001", @"883", @"001", @"886", @"TW", @"888", @"001", |
| @"960", @"MV", @"961", @"LB", @"962", @"JO", @"963", @"SY", @"964", @"IQ", @"965", @"KW", @"966", @"SA", @"967", @"YE", @"968", @"OM", |
| @"970", @"PS", @"971", @"AE", @"972", @"IL", @"973", @"BH", @"974", @"QA", @"975", @"BT", @"976", @"MN", @"977", @"NP", @"979", @"001", |
| @"992", @"TJ", @"993", @"TM", @"994", @"AZ", @"995", @"GE", @"996", @"KG", @"998", @"UZ", |
| nil]; |
| } |
| |
| /* |
| - (void)initCN2CC |
| { |
| [self clearCN2CC]; |
| _mapCN2CCode = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
| @[@"US", @"AG", @"AI", @"AS", @"BB", @"BM", @"BS", @"CA", @"DM", @"DO", @"GD", @"GU", @"JM", @"KN", @"KY", @"LC", @"MP", @"MS", @"PR", @"SX", @"TC", @"TT", @"VC", @"VG", @"VI"], @"1", @[@"RU", @"KZ"], @"7", |
| @[@"EG"], @"20", @[@"ZA"], @"27", |
| @[@"GR"], @"30", @[@"NL"], @"31", @[@"BE"], @"32", @[@"FR"], @"33", @[@"ES"], @"34", @[@"HU"], @"36", @[@"IT"], @"39", |
| @[@"RO"], @"40", @[@"CH"], @"41", @[@"AT"], @"43", @[@"GB", @"GG", @"IM", @"JE"], @"44", @[@"DK"], @"45", @[@"SE"], @"46", @[@"NO", @"SJ"], @"47", @[@"PL"], @"48", @[@"DE"], @"49", |
| @[@"PE"], @"51", @[@"MX"], @"52", @[@"CU"], @"53", @[@"AR"], @"54", @[@"BR"], @"55", @[@"CL"], @"56", @[@"CO"], @"57", @[@"VE"], @"58", |
| @[@"MY"], @"60", @[@"AU", @"CC", @"CX"], @"61", @[@"ID"], @"62", @[@"PH"], @"63", @[@"NZ"], @"64", @[@"SG"], @"65", @[@"TH"], @"66", |
| @[@"JP"], @"81", @[@"KR"], @"82", @[@"VN"], @"84", @[@"CN"], @"86", |
| @[@"TR"], @"90", @[@"IN"], @"91", @[@"PK"], @"92", @[@"AF"], @"93", @[@"LK"], @"94", @[@"MM"], @"95", @[@"IR"], @"98", |
| @[@"SS"], @"211", @[@"MA", @"EH"], @"212", @[@"DZ"], @"213", @[@"TN"], @"216", @[@"LY"], @"218", |
| @[@"GM"], @"220", @[@"SN"], @"221", @[@"MR"], @"222", @[@"ML"], @"223", @[@"GN"], @"224", @[@"CI"], @"225", @[@"BF"], @"226", @[@"NE"], @"227", @[@"TG"], @"228", @[@"BJ"], @"229", |
| @[@"MU"], @"230", @[@"LR"], @"231", @[@"SL"], @"232", @[@"GH"], @"233", @[@"NG"], @"234", @[@"TD"], @"235", @[@"CF"], @"236", @[@"CM"], @"237", @[@"CV"], @"238", @[@"ST"], @"239", |
| @[@"GQ"], @"240", @[@"GA"], @"241", @[@"CG"], @"242", @[@"CD"], @"243", @[@"AO"], @"244", @[@"GW"], @"245", @[@"IO"], @"246", @[@"AC"], @"247", @[@"SC"], @"248", @[@"SD"], @"249", |
| @[@"RW"], @"250", @[@"ET"], @"251", @[@"SO"], @"252", @[@"DJ"], @"253", @[@"KE"], @"254", @[@"TZ"], @"255", @[@"UG"], @"256", @[@"BI"], @"257", @[@"MZ"], @"258", |
| @[@"ZM"], @"260", @[@"MG"], @"261", @[@"RE", @"YT"], @"262", @[@"ZW"], @"263", @[@"NA"], @"264", @[@"MW"], @"265", @[@"LS"], @"266", @[@"BW"], @"267", @[@"SZ"], @"268", @[@"KM"], @"269", |
| @[@"SH"], @"290", @[@"ER"], @"291", @[@"AW"], @"297", @[@"FO"], @"298", @[@"GL"], @"299", |
| @[@"GI"], @"350", @[@"PT"], @"351", @[@"LU"], @"352", @[@"IE"], @"353", @[@"IS"], @"354", @[@"AL"], @"355", @[@"MT"], @"356", @[@"CY"], @"357", @[@"FI", @"AX"], @"358", @[@"BG"], @"359", |
| @[@"LT"], @"370", @[@"LV"], @"371", @[@"EE"], @"372", @[@"MD"], @"373", @[@"AM"], @"374", @[@"BY"], @"375", @[@"AD"], @"376", @[@"MC"], @"377", @[@"SM"], @"378", @[@"VA"], @"379", |
| @[@"UA"], @"380", @[@"RS"], @"381", @[@"ME"], @"382", @[@"HR"], @"385", @[@"SI"], @"386", @[@"BA"], @"387", @[@"MK"], @"389", |
| @[@"CZ"], @"420", @[@"SK"], @"421", @[@"LI"], @"423", |
| @[@"FK"], @"500", @[@"BZ"], @"501", @[@"GT"], @"502", @[@"SV"], @"503", @[@"HN"], @"504", @[@"NI"], @"505", @[@"CR"], @"506", @[@"PA"], @"507", @[@"PM"], @"508", @[@"HT"], @"509", |
| @[@"GP", @"BL", @"MF"], @"590", @[@"BO"], @"591", @[@"GY"], @"592", @[@"EC"], @"593", @[@"GF"], @"594", @[@"PY"], @"595", @[@"MQ"], @"596", @[@"SR"], @"597", @[@"UY"], @"598", @[@"CW", @"BQ"], @"599", |
| @[@"TL"], @"670", @[@"NF"], @"672", @[@"BN"], @"673", @[@"NR"], @"674", @[@"PG"], @"675", @[@"TO"], @"676", @[@"SB"], @"677", @[@"VU"], @"678", @[@"FJ"], @"679", |
| @[@"PW"], @"680", @[@"WF"], @"681", @[@"CK"], @"682", @[@"NU"], @"683", @[@"WS"], @"685", @[@"KI"], @"686", @[@"NC"], @"687", @[@"TV"], @"688", @[@"PF"], @"689", |
| @[@"TK"], @"690", @[@"FM"], @"691", @[@"MH"], @"692", |
| @[@"001"], @"800", @[@"001"], @"808", |
| @[@"KP"], @"850", @[@"HK"], @"852", @[@"MO"], @"853", @[@"KH"], @"855", @[@"LA"], @"856", |
| @[@"001"], @"870", @[@"001"], @"878", |
| @[@"BD"], @"880", @[@"001"], @"881", @[@"001"], @"882", @[@"001"], @"883", @[@"TW"], @"886", @[@"001"], @"888", |
| @[@"MV"], @"960", @[@"LB"], @"961", @[@"JO"], @"962", @[@"SY"], @"963", @[@"IQ"], @"964", @[@"KW"], @"965", @[@"SA"], @"966", @[@"YE"], @"967", @[@"OM"], @"968", |
| @[@"PS"], @"970", @[@"AE"], @"971", @[@"IL"], @"972", @[@"BH"], @"973", @[@"QA"], @"974", @[@"BT"], @"975", @[@"MN"], @"976", @[@"NP"], @"977", @[@"001"], @"979", |
| @[@"TJ"], @"992", @[@"TM"], @"993", @[@"AZ"], @"994", @[@"GE"], @"995", @[@"KG"], @"996", @[@"UZ"], @"998", nil]; |
| } |
| */ |
| |
| |
| #pragma mark - Metadata manager (phonenumberutil.js) functions - |
| /** |
| * Attempts to extract a possible number from the string passed in. This |
| * currently strips all leading characters that cannot be used to start a phone |
| * number. Characters that can be used to start a phone number are defined in |
| * the VALID_START_CHAR_PATTERN. If none of these characters are found in the |
| * number passed in, an empty string is returned. This function also attempts to |
| * strip off any alternative extensions or endings if two or more are present, |
| * such as in the case of: (530) 583-6985 x302/x2303. The second extension here |
| * makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985 |
| * x2303. We remove the second extension so that the first number is parsed |
| * correctly. |
| * |
| * @param {string} number the string that might contain a phone number. |
| * @return {string} the number, stripped of any non-phone-number prefix (such as |
| * 'Tel:') or an empty string if no character used to start phone numbers |
| * (such as + or any digit) is found in the number. |
| */ |
| - (NSString*)extractPossibleNumber:(NSString*)number |
| { |
| NSString *possibleNumber = @""; |
| NSInteger start = [self stringPositionByRegex:number regex:self.VALID_START_CHAR_PATTERN_]; |
| |
| if (start >= 0) |
| { |
| possibleNumber = [number substringFromIndex:start]; |
| // Remove trailing non-alpha non-numerical characters. |
| possibleNumber = [self replaceStringByRegex:possibleNumber regex:self.UNWANTED_END_CHAR_PATTERN_ withTemplate:@""]; |
| |
| // Check for extra numbers at the end. |
| NSInteger secondNumberStart = [self stringPositionByRegex:number regex:self.SECOND_NUMBER_START_PATTERN_]; |
| if (secondNumberStart >= 0) |
| { |
| possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, secondNumberStart - 1)]; |
| } |
| } |
| else |
| { |
| possibleNumber = @""; |
| } |
| |
| return possibleNumber; |
| |
| /* |
| NSString *possibleNumber = @""; |
| NSRegularExpression *currentPattern = self.VALID_START_CHAR_PATTERN_; |
| int sourceLength = phoneNumber.length; |
| |
| NSArray *matches = [currentPattern matchesInString:phoneNumber options:0 range:NSMakeRange(0, sourceLength)]; |
| if (matches && [matches count] > 0) |
| { |
| NSRange rangeOfFirstMatch = ((NSTextCheckingResult*)[matches objectAtIndex:0]).range; |
| possibleNumber = [phoneNumber substringFromIndex:rangeOfFirstMatch.location]; |
| |
| // Remove trailing non-alpha non-numerical characters. |
| currentPattern = self.UNWANTED_END_CHAR_PATTERN_; |
| possibleNumber = [currentPattern stringByReplacingMatchesInString:possibleNumber options:0 |
| range:NSMakeRange(0, [possibleNumber length]) withTemplate:@""]; |
| // Check for extra numbers at the end. |
| currentPattern = self.SECOND_NUMBER_START_PATTERN_; |
| matches = [currentPattern matchesInString:possibleNumber options:0 |
| range:NSMakeRange(0, [possibleNumber length])]; |
| if (matches && [matches count] > 0) |
| { |
| NSRange rangeOfSecondMatch = ((NSTextCheckingResult*)[matches objectAtIndex:0]).range; |
| possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, rangeOfSecondMatch.location)]; |
| } |
| } |
| |
| return possibleNumber; |
| */ |
| } |
| |
| |
| /** |
| * Checks to see if the string of characters could possibly be a phone number at |
| * all. At the moment, checks to see that the string begins with at least 2 |
| * digits, ignoring any punctuation commonly found in phone numbers. This method |
| * does not require the number to be normalized in advance - but does assume |
| * that leading non-number symbols have been removed, such as by the method |
| * extractPossibleNumber. |
| * |
| * @param {string} number string to be checked for viability as a phone number. |
| * @return {boolean} NO if the number could be a phone number of some sort, |
| * otherwise NO. |
| */ |
| - (BOOL)isViablePhoneNumber:(NSString*)phoneNumber |
| { |
| if (phoneNumber.length < MIN_LENGTH_FOR_NSN_) |
| { |
| return NO; |
| } |
| |
| return [self matchesEntirely:self.VALID_PHONE_NUMBER_PATTERN_ string:phoneNumber]; |
| } |
| |
| |
| /** |
| * Normalizes a string of characters representing a phone number. This performs |
| * the following conversions: |
| * Punctuation is stripped. |
| * For ALPHA/VANITY numbers: |
| * Letters are converted to their numeric representation on a telephone |
| * keypad. The keypad used here is the one defined in ITU Recommendation |
| * E.161. This is only done if there are 3 or more letters in the number, |
| * to lessen the risk that such letters are typos. |
| * For other numbers: |
| * Wide-ascii digits are converted to normal ASCII (European) digits. |
| * Arabic-Indic numerals are converted to European numerals. |
| * Spurious alpha characters are stripped. |
| * |
| * @param {string} number a string of characters representing a phone number. |
| * @return {string} the normalized string version of the phone number. |
| */ |
| - (NSString*)normalizePhoneNumber:(NSString*)number |
| { |
| if ([self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:number]) |
| { |
| return [self normalizeHelper:number normalizationReplacements:self.ALL_NORMALIZATION_MAPPINGS_ removeNonMatches:true]; |
| } |
| else |
| { |
| return [self normalizeDigitsOnly:number]; |
| } |
| |
| return nil; |
| } |
| |
| |
| /** |
| * Normalizes a string of characters representing a phone number. This is a |
| * wrapper for normalize(String number) but does in-place normalization of the |
| * StringBuffer provided. |
| * |
| * @param {!goog.string.StringBuffer} number a StringBuffer of characters |
| * representing a phone number that will be normalized in place. |
| * @private |
| */ |
| |
| - (void)normalizeSB:(NSString**)number |
| { |
| if (number == NULL) |
| { |
| return; |
| } |
| |
| (*number) = [self normalizePhoneNumber:(*number)]; |
| } |
| |
| |
| /** |
| * Normalizes a string of characters representing a phone number. This converts |
| * wide-ascii and arabic-indic numerals to European numerals, and strips |
| * punctuation and alpha characters. |
| * |
| * @param {string} number a string of characters representing a phone number. |
| * @return {string} the normalized string version of the phone number. |
| */ |
| - (NSString*)normalizeDigitsOnly:(NSString*)number |
| { |
| return [self stringByReplacingOccurrencesString:number |
| withMap:self.DIGIT_MAPPINGS removeNonMatches:YES]; |
| } |
| |
| |
| /** |
| * Converts all alpha characters in a number to their respective digits on a |
| * keypad, but retains existing formatting. Also converts wide-ascii digits to |
| * normal ascii digits, and converts Arabic-Indic numerals to European numerals. |
| * |
| * @param {string} number a string of characters representing a phone number. |
| * @return {string} the normalized string version of the phone number. |
| */ |
| - (NSString*)convertAlphaCharactersInNumber:(NSString*)number |
| { |
| return [self stringByReplacingOccurrencesString:number |
| withMap:self.ALL_NORMALIZATION_MAPPINGS_ removeNonMatches:NO]; |
| } |
| |
| |
| /** |
| * Gets the length of the geographical area code from the |
| * {@code national_number} field of the PhoneNumber object passed in, so that |
| * clients could use it to split a national significant number into geographical |
| * area code and subscriber number. It works in such a way that the resultant |
| * subscriber number should be diallable, at least on some devices. An example |
| * of how this could be used: |
| * |
| * <pre> |
| * var phoneUtil = getInstance(); |
| * var number = phoneUtil.parse('16502530000', 'US'); |
| * var nationalSignificantNumber = |
| * phoneUtil.getNationalSignificantNumber(number); |
| * var areaCode; |
| * var subscriberNumber; |
| * |
| * var areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number); |
| * if (areaCodeLength > 0) { |
| * areaCode = nationalSignificantNumber.substring(0, areaCodeLength); |
| * subscriberNumber = nationalSignificantNumber.substring(areaCodeLength); |
| * } else { |
| * areaCode = ''; |
| * subscriberNumber = nationalSignificantNumber; |
| * } |
| * </pre> |
| * |
| * N.B.: area code is a very ambiguous concept, so the I18N team generally |
| * recommends against using it for most purposes, but recommends using the more |
| * general {@code national_number} instead. Read the following carefully before |
| * deciding to use this method: |
| * <ul> |
| * <li> geographical area codes change over time, and this method honors those |
| * changes; therefore, it doesn't guarantee the stability of the result it |
| * produces. |
| * <li> subscriber numbers may not be diallable from all devices (notably |
| * mobile devices, which typically requires the full national_number to be |
| * dialled in most regions). |
| * <li> most non-geographical numbers have no area codes, including numbers |
| * from non-geographical entities. |
| * <li> some geographical numbers have no area codes. |
| * </ul> |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for |
| * which clients want to know the length of the area code. |
| * @return {number} the length of area code of the PhoneNumber object passed in. |
| */ |
| - (NSUInteger)getLengthOfGeographicalAreaCode:(NBPhoneNumber*)phoneNumber error:(NSError **)error |
| { |
| NSUInteger res = 0; |
| @try { |
| res = [self getLengthOfGeographicalAreaCode:phoneNumber]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| { |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| } |
| return res; |
| } |
| |
| |
| - (NSUInteger)getLengthOfGeographicalAreaCode:(NBPhoneNumber*)phoneNumber |
| { |
| NSString *regionCode = [self getRegionCodeForNumber:phoneNumber]; |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode]; |
| |
| if (metadata == nil) |
| { |
| return 0; |
| } |
| // If a country doesn't use a national prefix, and this number doesn't have |
| // an Italian leading zero, we assume it is a closed dialling plan with no |
| // area codes. |
| if (metadata.nationalPrefix == nil && phoneNumber.italianLeadingZero == NO) |
| { |
| return 0; |
| } |
| |
| if ([self isNumberGeographical:phoneNumber] == NO) |
| { |
| return 0; |
| } |
| |
| return [self getLengthOfNationalDestinationCode:phoneNumber]; |
| } |
| |
| |
| /** |
| * Gets the length of the national destination code (NDC) from the PhoneNumber |
| * object passed in, so that clients could use it to split a national |
| * significant number into NDC and subscriber number. The NDC of a phone number |
| * is normally the first group of digit(s) right after the country calling code |
| * when the number is formatted in the international format, if there is a |
| * subscriber number part that follows. An example of how this could be used: |
| * |
| * <pre> |
| * var phoneUtil = getInstance(); |
| * var number = phoneUtil.parse('18002530000', 'US'); |
| * var nationalSignificantNumber = |
| * phoneUtil.getNationalSignificantNumber(number); |
| * var nationalDestinationCode; |
| * var subscriberNumber; |
| * |
| * var nationalDestinationCodeLength = |
| * phoneUtil.getLengthOfNationalDestinationCode(number); |
| * if (nationalDestinationCodeLength > 0) { |
| * nationalDestinationCode = |
| * nationalSignificantNumber.substring(0, nationalDestinationCodeLength); |
| * subscriberNumber = |
| * nationalSignificantNumber.substring(nationalDestinationCodeLength); |
| * } else { |
| * nationalDestinationCode = ''; |
| * subscriberNumber = nationalSignificantNumber; |
| * } |
| * </pre> |
| * |
| * Refer to the unittests to see the difference between this function and |
| * {@link #getLengthOfGeographicalAreaCode}. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for |
| * which clients want to know the length of the NDC. |
| * @return {number} the length of NDC of the PhoneNumber object passed in. |
| */ |
| - (NSUInteger)getLengthOfNationalDestinationCode:(NBPhoneNumber*)phoneNumber error:(NSError **)error |
| { |
| NSUInteger res = 0; |
| |
| @try { |
| res = [self getLengthOfNationalDestinationCode:phoneNumber]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| { |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| } |
| |
| return res; |
| } |
| |
| |
| - (NSUInteger)getLengthOfNationalDestinationCode:(NBPhoneNumber*)phoneNumber |
| { |
| NBPhoneNumber *copiedProto = nil; |
| if ([self hasValue:phoneNumber.extension]) |
| { |
| copiedProto = [phoneNumber copy]; |
| copiedProto.extension = nil; |
| } |
| else |
| { |
| copiedProto = phoneNumber; |
| } |
| |
| NSString *nationalSignificantNumber = [self format:copiedProto numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| NSMutableArray *numberGroups = [[self componentsSeparatedByRegex:nationalSignificantNumber regex:NON_DIGITS_PATTERN_] mutableCopy]; |
| |
| // The pattern will start with '+COUNTRY_CODE ' so the first group will always |
| // be the empty string (before the + symbol) and the second group will be the |
| // country calling code. The third group will be area code if it is not the |
| // last group. |
| // NOTE: On IE the first group that is supposed to be the empty string does |
| // not appear in the array of number groups... so make the result on non-IE |
| // browsers to be that of IE. |
| if ([numberGroups count] > 0 && ((NSString*)[numberGroups objectAtIndex:0]).length <= 0) |
| { |
| [numberGroups removeObjectAtIndex:0]; |
| } |
| |
| if ([numberGroups count] <= 2) |
| { |
| return 0; |
| } |
| |
| NSArray *regionCodes = [self regionCodeFromCountryCode:phoneNumber.countryCode]; |
| BOOL isExists = NO; |
| |
| for (NSString *regCode in regionCodes) |
| { |
| if ([regCode isEqualToString:@"AR"]) |
| { |
| isExists = YES; |
| break; |
| } |
| } |
| |
| if (isExists && [self getNumberType:phoneNumber] == NBEPhoneNumberTypeMOBILE) |
| { |
| // Argentinian mobile numbers, when formatted in the international format, |
| // are in the form of +54 9 NDC XXXX.... As a result, we take the length of |
| // the third group (NDC) and add 1 for the digit 9, which also forms part of |
| // the national significant number. |
| // |
| // TODO: Investigate the possibility of better modeling the metadata to make |
| // it easier to obtain the NDC. |
| return ((NSString*)[numberGroups objectAtIndex:2]).length + 1; |
| } |
| |
| return ((NSString*)[numberGroups objectAtIndex:1]).length; |
| } |
| |
| |
| /** |
| * Normalizes a string of characters representing a phone number by replacing |
| * all characters found in the accompanying map with the values therein, and |
| * stripping all other characters if removeNonMatches is NO. |
| * |
| * @param {string} number a string of characters representing a phone number. |
| * @param {!Object.<string, string>} normalizationReplacements a mapping of |
| * characters to what they should be replaced by in the normalized version |
| * of the phone number. |
| * @param {boolean} removeNonMatches indicates whether characters that are not |
| * able to be replaced should be stripped from the number. If this is NO, |
| * they will be left unchanged in the number. |
| * @return {string} the normalized string version of the phone number. |
| * @private |
| */ |
| - (NSString*)normalizeHelper:(NSString*)sourceString normalizationReplacements:(NSDictionary*)normalizationReplacements |
| removeNonMatches:(BOOL)removeNonMatches |
| { |
| NSMutableString *normalizedNumber = [[NSMutableString alloc] init]; |
| unichar character = 0; |
| NSString *newDigit = @""; |
| NSUInteger numberLength = sourceString.length; |
| |
| for (NSUInteger i = 0; i<numberLength; ++i) |
| { |
| character = [sourceString characterAtIndex:i]; |
| newDigit = [normalizationReplacements objectForKey:[[NSString stringWithFormat: @"%C", character] uppercaseString]]; |
| if (newDigit != nil) |
| { |
| [normalizedNumber appendString:newDigit]; |
| } |
| else if (removeNonMatches == NO) |
| { |
| [normalizedNumber appendString:[NSString stringWithFormat: @"%C", character]]; |
| } |
| // If neither of the above are NO, we remove this character. |
| |
| //NSLog(@"[%@]", normalizedNumber); |
| } |
| |
| return normalizedNumber; |
| } |
| |
| |
| /** |
| * Helper function to check if the national prefix formatting rule has the first |
| * group only, i.e., does not start with the national prefix. |
| * |
| * @param {string} nationalPrefixFormattingRule The formatting rule for the |
| * national prefix. |
| * @return {boolean} NO if the national prefix formatting rule has the first |
| * group only. |
| */ |
| - (BOOL)formattingRuleHasFirstGroupOnly:(NSString*)nationalPrefixFormattingRule |
| { |
| BOOL hasFound = NO; |
| if ([self stringPositionByRegex:nationalPrefixFormattingRule regex:FIRST_GROUP_ONLY_PREFIX_PATTERN_] >= 0) |
| { |
| hasFound = YES; |
| } |
| |
| return (([nationalPrefixFormattingRule length] == 0) || hasFound); |
| } |
| |
| |
| /** |
| * Tests whether a phone number has a geographical association. It checks if |
| * the number is associated to a certain region in the country where it belongs |
| * to. Note that this doesn't verify if the number is actually in use. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} phoneNumber The phone number to test. |
| * @return {boolean} NO if the phone number has a geographical association. |
| * @private |
| */ |
| - (BOOL)isNumberGeographical:(NBPhoneNumber*)phoneNumber |
| { |
| NBEPhoneNumberType numberType = [self getNumberType:phoneNumber]; |
| // TODO: Include mobile phone numbers from countries like Indonesia, which |
| // has some mobile numbers that are geographical. |
| return numberType == NBEPhoneNumberTypeFIXED_LINE || numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE; |
| } |
| |
| |
| /** |
| * Helper function to check region code is not unknown or nil. |
| * |
| * @param {?string} regionCode the ISO 3166-1 two-letter region code. |
| * @return {boolean} NO if region code is valid. |
| * @private |
| */ |
| - (BOOL)isValidRegionCode:(NSString*)regionCode |
| { |
| // In Java we check whether the regionCode is contained in supportedRegions |
| // that is built out of all the values of countryCallingCodeToRegionCodeMap |
| // (countryCodeToRegionCodeMap in JS) minus REGION_CODE_FOR_NON_GEO_ENTITY. |
| // In JS we check whether the regionCode is contained in the keys of |
| // countryToMetadata but since for non-geographical country calling codes |
| // (e.g. +800) we use the country calling codes instead of the region code as |
| // key in the map we have to make sure regionCode is not a number to prevent |
| // returning NO for non-geographical country calling codes. |
| return [self hasValue:regionCode] && [self isNaN:regionCode] && [self getMetadataForRegion:regionCode.uppercaseString] != nil; |
| } |
| |
| |
| /** |
| * Helper function to check the country calling code is valid. |
| * |
| * @param {number} countryCallingCode the country calling code. |
| * @return {boolean} NO if country calling code code is valid. |
| * @private |
| */ |
| - (BOOL)hasValidCountryCallingCode:(NSNumber *)countryCallingCode |
| { |
| id res = [self regionCodeFromCountryCode:countryCallingCode]; |
| if (res != nil) |
| { |
| return YES; |
| } |
| |
| return NO; |
| } |
| |
| |
| /** |
| * Formats a phone number in the specified format using default rules. Note that |
| * this does not promise to produce a phone number that the user can dial from |
| * where they are - although we do format in either 'national' or |
| * 'international' format depending on what the client asks for, we do not |
| * currently support a more abbreviated format, such as for users in the same |
| * 'area' who could potentially dial the number without area code. Note that if |
| * the phone number has a country calling code of 0 or an otherwise invalid |
| * country calling code, we cannot work out which formatting rules to apply so |
| * we return the national significant number with no formatting applied. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @return {string} the formatted phone number. |
| */ |
| - (NSString*)format:(NBPhoneNumber*)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat error:(NSError**)error; |
| { |
| NSString *res = nil; |
| @try { |
| res = [self format:phoneNumber numberFormat:numberFormat]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NSString*)format:(NBPhoneNumber*)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat |
| { |
| if ([phoneNumber.nationalNumber isEqualToNumber:@0] && [self hasValue:phoneNumber.rawInput]) |
| { |
| // Unparseable numbers that kept their raw input just use that. |
| // This is the only case where a number can be formatted as E164 without a |
| // leading '+' symbol (but the original number wasn't parseable anyway). |
| // TODO: Consider removing the 'if' above so that unparseable strings |
| // without raw input format to the empty string instead of "+00" |
| /** @type {string} */ |
| NSString *rawInput = phoneNumber.rawInput; |
| if ([self hasValue:rawInput]) { |
| return rawInput; |
| } |
| } |
| |
| NSNumber *countryCallingCode = phoneNumber.countryCode; |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber]; |
| |
| if (numberFormat == NBEPhoneNumberFormatE164) |
| { |
| // Early exit for E164 case (even if the country calling code is invalid) |
| // since no formatting of the national number needs to be applied. |
| // Extensions are not formatted. |
| return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatE164 |
| formattedNationalNumber:nationalSignificantNumber formattedExtension:@""]; |
| } |
| |
| if ([self hasValidCountryCallingCode:countryCallingCode] == NO) |
| { |
| return nationalSignificantNumber; |
| } |
| |
| // Note getRegionCodeForCountryCode() is used because formatting information |
| // for regions which share a country calling code is contained by only one |
| // region for performance reasons. For example, for NANPA regions it will be |
| // contained in the metadata for US. |
| NSArray *regionCodeArray = [self regionCodeFromCountryCode:countryCallingCode]; |
| NSString *regionCode = [regionCodeArray objectAtIndex:0]; |
| |
| // Metadata cannot be nil because the country calling code is valid (which |
| // means that the region code cannot be ZZ and must be one of our supported |
| // region codes). |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode]; |
| NSString *formattedExtension = [self maybeGetFormattedExtension:phoneNumber metadata:metadata numberFormat:numberFormat]; |
| NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadata phoneNumberFormat:numberFormat carrierCode:nil]; |
| |
| return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:numberFormat |
| formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension]; |
| } |
| |
| |
| /** |
| * Formats a phone number in the specified format using client-defined |
| * formatting rules. Note that if the phone number has a country calling code of |
| * zero or an otherwise invalid country calling code, we cannot work out things |
| * like whether there should be a national prefix applied, or how to format |
| * extensions, so we return the national significant number with no formatting |
| * applied. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting |
| * rules specified by clients. |
| * @return {string} the formatted phone number. |
| */ |
| - (NSString*)formatByPattern:(NBPhoneNumber*)number numberFormat:(NBEPhoneNumberFormat)numberFormat userDefinedFormats:(NSArray*)userDefinedFormats error:(NSError**)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatByPattern:number numberFormat:numberFormat userDefinedFormats:userDefinedFormats]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NSString*)formatByPattern:(NBPhoneNumber*)number numberFormat:(NBEPhoneNumberFormat)numberFormat userDefinedFormats:(NSArray*)userDefinedFormats |
| { |
| NSNumber *countryCallingCode = number.countryCode; |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number]; |
| |
| if ([self hasValidCountryCallingCode:countryCallingCode] == NO) |
| { |
| return nationalSignificantNumber; |
| } |
| |
| // Note getRegionCodeForCountryCode() is used because formatting information |
| // for regions which share a country calling code is contained by only one |
| // region for performance reasons. For example, for NANPA regions it will be |
| // contained in the metadata for US. |
| NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode]; |
| NSString *regionCode = nil; |
| if (regionCodes != nil && regionCodes.count > 0) |
| { |
| regionCode = [regionCodes objectAtIndex:0]; |
| } |
| |
| // Metadata cannot be nil because the country calling code is valid |
| /** @type {i18n.phonenumbers.PhoneMetadata} */ |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode]; |
| |
| NSString *formattedNumber = @""; |
| NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:userDefinedFormats nationalNumber:nationalSignificantNumber]; |
| if (formattingPattern == nil) |
| { |
| // If no pattern above is matched, we format the number as a whole. |
| formattedNumber = nationalSignificantNumber; |
| } |
| else |
| { |
| // Before we do a replacement of the national prefix pattern $NP with the |
| // national prefix, we need to copy the rule so that subsequent replacements |
| // for different numbers have the appropriate national prefix. |
| NBNumberFormat *numFormatCopy = [formattingPattern copy]; |
| NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule; |
| |
| if (nationalPrefixFormattingRule.length > 0) |
| { |
| NSString *nationalPrefix = metadata.nationalPrefix; |
| if (nationalPrefix.length > 0) |
| { |
| // Replace $NP with national prefix and $FG with the first group ($1). |
| nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule regex:NP_PATTERN_ withTemplate:nationalPrefix]; |
| nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule regex:FG_PATTERN_ withTemplate:@"\\$1"]; |
| numFormatCopy.nationalPrefixFormattingRule = nationalPrefixFormattingRule; |
| } |
| else |
| { |
| // We don't want to have a rule for how to format the national prefix if |
| // there isn't one. |
| numFormatCopy.nationalPrefixFormattingRule = @""; |
| } |
| } |
| |
| formattedNumber = [self formatNsnUsingPattern:nationalSignificantNumber |
| formattingPattern:numFormatCopy numberFormat:numberFormat carrierCode:nil]; |
| } |
| |
| NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadata numberFormat:numberFormat]; |
| |
| //NSLog(@"!@# prefixNumberWithCountryCallingCode called [%@]", formattedExtension); |
| return [self prefixNumberWithCountryCallingCode:countryCallingCode |
| phoneNumberFormat:numberFormat |
| formattedNationalNumber:formattedNumber |
| formattedExtension:formattedExtension]; |
| } |
| |
| |
| /** |
| * Formats a phone number in national format for dialing using the carrier as |
| * specified in the {@code carrierCode}. The {@code carrierCode} will always be |
| * used regardless of whether the phone number already has a preferred domestic |
| * carrier code stored. If {@code carrierCode} contains an empty string, returns |
| * the number in national format without any carrier code. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {string} carrierCode the carrier selection code to be used. |
| * @return {string} the formatted phone number in national format for dialing |
| * using the carrier as specified in the {@code carrierCode}. |
| */ |
| - (NSString*)formatNationalNumberWithCarrierCode:(NBPhoneNumber*)number carrierCode:(NSString*)carrierCode error:(NSError **)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatNationalNumberWithCarrierCode:number carrierCode:carrierCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NSString*)formatNationalNumberWithCarrierCode:(NBPhoneNumber*)number carrierCode:(NSString*)carrierCode |
| { |
| NSNumber *countryCallingCode = number.countryCode; |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number]; |
| if ([self hasValidCountryCallingCode:countryCallingCode] == NO) |
| { |
| return nationalSignificantNumber; |
| } |
| |
| // Note getRegionCodeForCountryCode() is used because formatting information |
| // for regions which share a country calling code is contained by only one |
| // region for performance reasons. For example, for NANPA regions it will be |
| // contained in the metadata for US. |
| NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode]; |
| // Metadata cannot be nil because the country calling code is valid. |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode]; |
| NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadata numberFormat:NBEPhoneNumberFormatNATIONAL]; |
| NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadata phoneNumberFormat:NBEPhoneNumberFormatNATIONAL carrierCode:carrierCode]; |
| return [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatNATIONAL formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension]; |
| } |
| |
| |
| /** |
| * @param {number} countryCallingCode |
| * @param {?string} regionCode |
| * @return {i18n.phonenumbers.PhoneMetadata} |
| * @private |
| */ |
| - (NBPhoneMetaData*)getMetadataForRegionOrCallingCode:(NSNumber*)countryCallingCode regionCode:(NSString*)regionCode |
| { |
| return [_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] ? |
| [self getMetadataForNonGeographicalRegion:countryCallingCode] : [self getMetadataForRegion:regionCode]; |
| } |
| |
| |
| /** |
| * Formats a phone number in national format for dialing using the carrier as |
| * specified in the preferred_domestic_carrier_code field of the PhoneNumber |
| * object passed in. If that is missing, use the {@code fallbackCarrierCode} |
| * passed in instead. If there is no {@code preferred_domestic_carrier_code}, |
| * and the {@code fallbackCarrierCode} contains an empty string, return the |
| * number in national format without any carrier code. |
| * |
| * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier |
| * code passed in should take precedence over the number's |
| * {@code preferred_domestic_carrier_code} when formatting. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {string} fallbackCarrierCode the carrier selection code to be used, if |
| * none is found in the phone number itself. |
| * @return {string} the formatted phone number in national format for dialing |
| * using the number's preferred_domestic_carrier_code, or the |
| * {@code fallbackCarrierCode} passed in if none is found. |
| */ |
| - (NSString*)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber*)number |
| fallbackCarrierCode:(NSString*)fallbackCarrierCode error:(NSError **)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatNationalNumberWithCarrierCode:number carrierCode:fallbackCarrierCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return res; |
| } |
| |
| |
| - (NSString*)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber*)number fallbackCarrierCode:(NSString*)fallbackCarrierCode |
| { |
| NSString *domesticCarrierCode = number.preferredDomesticCarrierCode != nil ? number.preferredDomesticCarrierCode : fallbackCarrierCode; |
| return [self formatNationalNumberWithCarrierCode:number carrierCode:domesticCarrierCode]; |
| } |
| |
| |
| /** |
| * Returns a number formatted in such a way that it can be dialed from a mobile |
| * phone in a specific region. If the number cannot be reached from the region |
| * (e.g. some countries block toll-free numbers from being called outside of the |
| * country), the method returns an empty string. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {string} regionCallingFrom the region where the call is being placed. |
| * @param {boolean} withFormatting whether the number should be returned with |
| * formatting symbols, such as spaces and dashes. |
| * @return {string} the formatted phone number. |
| */ |
| - (NSString*)formatNumberForMobileDialing:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom withFormatting:(BOOL)withFormatting error:(NSError**)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatNumberForMobileDialing:number regionCallingFrom:regionCallingFrom withFormatting:withFormatting]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NSString*)formatNumberForMobileDialing:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom withFormatting:(BOOL)withFormatting |
| { |
| NSNumber *countryCallingCode = number.countryCode; |
| if ([self hasValidCountryCallingCode:countryCallingCode] == NO) |
| { |
| return [self hasValue:number.rawInput] ? number.rawInput : @""; |
| } |
| |
| NSString *formattedNumber = @""; |
| // Clear the extension, as that part cannot normally be dialed together with |
| // the main number. |
| NBPhoneNumber *numberNoExt = [number copy]; |
| numberNoExt.extension = @""; |
| |
| NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode]; |
| if ([regionCallingFrom isEqualToString:regionCode]) |
| { |
| NBEPhoneNumberType numberType = [self getNumberType:numberNoExt]; |
| BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE) || (numberType == NBEPhoneNumberTypeMOBILE) || (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE); |
| // Carrier codes may be needed in some countries. We handle this here. |
| if ([regionCode isEqualToString:@"CO"] && numberType == NBEPhoneNumberTypeFIXED_LINE) |
| { |
| formattedNumber = [self formatNationalNumberWithCarrierCode:numberNoExt |
| carrierCode:COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX_]; |
| } |
| else if ([regionCode isEqualToString:@"BR"] && isFixedLineOrMobile) |
| { |
| formattedNumber = [self hasValue:numberNoExt.preferredDomesticCarrierCode] ? |
| [self formatNationalNumberWithPreferredCarrierCode:numberNoExt fallbackCarrierCode:@""] : @""; |
| // Brazilian fixed line and mobile numbers need to be dialed with a |
| // carrier code when called within Brazil. Without that, most of the |
| // carriers won't connect the call. Because of that, we return an |
| // empty string here. |
| } |
| else |
| { |
| // For NANPA countries, non-geographical countries, and Mexican fixed |
| // line and mobile numbers, we output international format for numbersi |
| // that can be dialed internationally as that always works. |
| if (([countryCallingCode isEqualToNumber:@(NANPA_COUNTRY_CODE_)] || |
| [regionCode isEqualToString:_REGION_CODE_FOR_NON_GEO_ENTITY] || |
| // MX fixed line and mobile numbers should always be formatted in |
| // international format, even when dialed within MX. For national |
| // format to work, a carrier code needs to be used, and the correct |
| // carrier code depends on if the caller and callee are from the |
| // same local area. It is trickier to get that to work correctly than |
| // using international format, which is tested to work fine on all |
| // carriers. |
| ([regionCode isEqualToString:@"MX"] && isFixedLineOrMobile)) && [self canBeInternationallyDialled:numberNoExt]) |
| { |
| formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| } |
| else |
| { |
| formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatNATIONAL]; |
| } |
| } |
| } |
| else if ([self canBeInternationallyDialled:numberNoExt]) |
| { |
| return withFormatting ? [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL] : |
| [self format:numberNoExt numberFormat:NBEPhoneNumberFormatE164]; |
| } |
| |
| return withFormatting ? |
| formattedNumber : [self normalizeHelper:formattedNumber normalizationReplacements:self.DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES]; |
| } |
| |
| |
| /** |
| * Formats a phone number for out-of-country dialing purposes. If no |
| * regionCallingFrom is supplied, we format the number in its INTERNATIONAL |
| * format. If the country calling code is the same as that of the region where |
| * the number is from, then NATIONAL formatting will be applied. |
| * |
| * <p>If the number itself has a country calling code of zero or an otherwise |
| * invalid country calling code, then we return the number with no formatting |
| * applied. |
| * |
| * <p>Note this function takes care of the case for calling inside of NANPA and |
| * between Russia and Kazakhstan (who share the same country calling code). In |
| * those cases, no international prefix is used. For regions which have multiple |
| * international prefixes, the number in its INTERNATIONAL format will be |
| * returned instead. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be |
| * formatted. |
| * @param {string} regionCallingFrom the region where the call is being placed. |
| * @return {string} the formatted phone number. |
| */ |
| - (NSString*)formatOutOfCountryCallingNumber:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError**)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return res; |
| } |
| |
| - (NSString*)formatOutOfCountryCallingNumber:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom |
| { |
| if ([self isValidRegionCode:regionCallingFrom] == NO) |
| { |
| return [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| } |
| |
| NSNumber *countryCallingCode = number.countryCode; |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number]; |
| if ([self hasValidCountryCallingCode:countryCallingCode] == NO) |
| { |
| return nationalSignificantNumber; |
| } |
| |
| if ([countryCallingCode isEqualToNumber: @(NANPA_COUNTRY_CODE_)]) |
| { |
| if ([self isNANPACountry:regionCallingFrom]) |
| { |
| // For NANPA regions, return the national format for these regions but |
| // prefix it with the country calling code. |
| return [NSString stringWithFormat:@"%@ %@", countryCallingCode, [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]]; |
| } |
| } |
| else if ([countryCallingCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCallingFrom]]) |
| { |
| // If regions share a country calling code, the country calling code need |
| // not be dialled. This also applies when dialling within a region, so this |
| // if clause covers both these cases. Technically this is the case for |
| // dialling from La Reunion to other overseas departments of France (French |
| // Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover |
| // this edge case for now and for those cases return the version including |
| // country calling code. Details here: |
| // http://www.petitfute.com/voyage/225-info-pratiques-reunion |
| return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]; |
| } |
| // Metadata cannot be nil because we checked 'isValidRegionCode()' above. |
| NBPhoneMetaData *metadataForRegionCallingFrom = [self getMetadataForRegion:regionCallingFrom]; |
| NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix; |
| |
| // For regions that have multiple international prefixes, the international |
| // format of the number is returned, unless there is a preferred international |
| // prefix. |
| NSString *internationalPrefixForFormatting = @""; |
| if ([self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX_ string:internationalPrefix]) |
| { |
| internationalPrefixForFormatting = internationalPrefix; |
| } |
| else if ([self hasValue:metadataForRegionCallingFrom.preferredInternationalPrefix]) |
| { |
| internationalPrefixForFormatting = metadataForRegionCallingFrom.preferredInternationalPrefix; |
| } |
| |
| NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode]; |
| // Metadata cannot be nil because the country calling code is valid. |
| NBPhoneMetaData *metadataForRegion = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode]; |
| NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber metadata:metadataForRegion |
| phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL carrierCode:nil]; |
| NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadataForRegion numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| |
| NSString *hasLenth = [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCallingCode, formattedNationalNumber, formattedExtension]; |
| NSString *hasNotLength = [self prefixNumberWithCountryCallingCode:countryCallingCode phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL |
| formattedNationalNumber:formattedNationalNumber formattedExtension:formattedExtension]; |
| |
| return internationalPrefixForFormatting.length > 0 ? hasLenth:hasNotLength; |
| } |
| |
| |
| /** |
| * A helper function that is used by format and formatByPattern. |
| * |
| * @param {number} countryCallingCode the country calling code. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @param {string} formattedNationalNumber |
| * @param {string} formattedExtension |
| * @return {string} the formatted phone number. |
| * @private |
| */ |
| - (NSString*)prefixNumberWithCountryCallingCode:(NSNumber*)countryCallingCode phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat |
| formattedNationalNumber:(NSString*)formattedNationalNumber |
| formattedExtension:(NSString*)formattedExtension |
| { |
| switch (numberFormat) |
| { |
| case NBEPhoneNumberFormatE164: |
| return [NSString stringWithFormat:@"+%@%@%@", countryCallingCode, formattedNationalNumber, formattedExtension]; |
| case NBEPhoneNumberFormatINTERNATIONAL: |
| return [NSString stringWithFormat:@"+%@ %@%@", countryCallingCode, formattedNationalNumber, formattedExtension]; |
| case NBEPhoneNumberFormatRFC3966: |
| return [NSString stringWithFormat:@"%@+%@-%@%@", RFC3966_PREFIX_, countryCallingCode, formattedNationalNumber, formattedExtension]; |
| case NBEPhoneNumberFormatNATIONAL: |
| default: |
| return [NSString stringWithFormat:@"%@%@", formattedNationalNumber, formattedExtension]; |
| } |
| } |
| |
| |
| /** |
| * Formats a phone number using the original phone number format that the number |
| * is parsed from. The original format is embedded in the country_code_source |
| * field of the PhoneNumber object passed in. If such information is missing, |
| * the number will be formatted into the NATIONAL format by default. When the |
| * number contains a leading zero and this is unexpected for this country, or we |
| * don't have a formatting pattern for the number, the method returns the raw |
| * input when it is available. |
| * |
| * Note this method guarantees no digit will be inserted, removed or modified as |
| * a result of formatting. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to |
| * be formatted in its original number format. |
| * @param {string} regionCallingFrom the region whose IDD needs to be prefixed |
| * if the original number has one. |
| * @return {string} the formatted phone number in its original number format. |
| */ |
| - (NSString*)formatInOriginalFormat:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError **)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatInOriginalFormat:number regionCallingFrom:regionCallingFrom]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return res; |
| } |
| |
| |
| - (NSString*)formatInOriginalFormat:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom |
| { |
| if ([self hasValue:number.rawInput] && ([self hasUnexpectedItalianLeadingZero:number] || [self hasFormattingPatternForNumber:number] == NO)) |
| { |
| // We check if we have the formatting pattern because without that, we might |
| // format the number as a group without national prefix. |
| return number.rawInput; |
| } |
| |
| if (number.countryCodeSource == nil) |
| { |
| return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]; |
| } |
| |
| NSString *formattedNumber = @""; |
| |
| switch ([number.countryCodeSource intValue]) |
| { |
| case NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN: |
| formattedNumber = [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| break; |
| case NBECountryCodeSourceFROM_NUMBER_WITH_IDD: |
| formattedNumber = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom]; |
| break; |
| case NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN: |
| formattedNumber = [[self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL] substringFromIndex:1]; |
| break; |
| case NBECountryCodeSourceFROM_DEFAULT_COUNTRY: |
| // Fall-through to default case. |
| default: |
| { |
| NSString *regionCode = [self getRegionCodeForCountryCode:number.countryCode]; |
| // We strip non-digits from the NDD here, and from the raw input later, |
| // so that we can compare them easily. |
| NSString *nationalPrefix = [self getNddPrefixForRegion:regionCode stripNonDigits:YES]; |
| NSString *nationalFormat = [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]; |
| if (nationalPrefix == nil || nationalPrefix.length == 0) |
| { |
| // If the region doesn't have a national prefix at all, we can safely |
| // return the national format without worrying about a national prefix |
| // being added. |
| formattedNumber = nationalFormat; |
| break; |
| } |
| // Otherwise, we check if the original number was entered with a national |
| // prefix. |
| if ([self rawInputContainsNationalPrefix:number.rawInput nationalPrefix:nationalPrefix regionCode:regionCode]) |
| { |
| // If so, we can safely return the national format. |
| formattedNumber = nationalFormat; |
| break; |
| } |
| // Metadata cannot be nil here because getNddPrefixForRegion() (above) |
| // returns nil if there is no metadata for the region. |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode]; |
| NSString *nationalNumber = [self getNationalSignificantNumber:number]; |
| NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber]; |
| // The format rule could still be nil here if the national number was 0 |
| // and there was no raw input (this should not be possible for numbers |
| // generated by the phonenumber library as they would also not have a |
| // country calling code and we would have exited earlier). |
| if (formatRule == nil) |
| { |
| formattedNumber = nationalFormat; |
| break; |
| } |
| // When the format we apply to this number doesn't contain national |
| // prefix, we can just return the national format. |
| // TODO: Refactor the code below with the code in |
| // isNationalPrefixPresentIfRequired. |
| NSString *candidateNationalPrefixRule = formatRule.nationalPrefixFormattingRule; |
| // We assume that the first-group symbol will never be _before_ the |
| // national prefix. |
| NSRange firstGroupRange = [candidateNationalPrefixRule rangeOfString:@"$1"]; |
| if (firstGroupRange.location == NSNotFound) |
| { |
| formattedNumber = nationalFormat; |
| break; |
| } |
| |
| if (firstGroupRange.location <= 0) |
| { |
| formattedNumber = nationalFormat; |
| break; |
| } |
| candidateNationalPrefixRule = [candidateNationalPrefixRule substringWithRange:NSMakeRange(0, firstGroupRange.location)]; |
| candidateNationalPrefixRule = [self normalizeDigitsOnly:candidateNationalPrefixRule]; |
| if (candidateNationalPrefixRule.length == 0) |
| { |
| // National prefix not used when formatting this number. |
| formattedNumber = nationalFormat; |
| break; |
| } |
| // Otherwise, we need to remove the national prefix from our output. |
| NBNumberFormat *numFormatCopy = [formatRule copy]; |
| numFormatCopy.nationalPrefixFormattingRule = nil; |
| formattedNumber = [self formatByPattern:number numberFormat:NBEPhoneNumberFormatNATIONAL userDefinedFormats:@[numFormatCopy]]; |
| break; |
| } |
| } |
| |
| NSString *rawInput = number.rawInput; |
| // If no digit is inserted/removed/modified as a result of our formatting, we |
| // return the formatted phone number; otherwise we return the raw input the |
| // user entered. |
| if (formattedNumber != nil && rawInput.length > 0) |
| { |
| NSString *normalizedFormattedNumber = [self normalizeHelper:formattedNumber normalizationReplacements:_DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES]; |
| /** @type {string} */ |
| NSString *normalizedRawInput =[self normalizeHelper:rawInput normalizationReplacements:_DIALLABLE_CHAR_MAPPINGS_ removeNonMatches:YES]; |
| |
| if ([normalizedFormattedNumber isEqualToString:normalizedRawInput] == NO) |
| { |
| formattedNumber = rawInput; |
| } |
| } |
| return formattedNumber; |
| } |
| |
| |
| /** |
| * Check if rawInput, which is assumed to be in the national format, has a |
| * national prefix. The national prefix is assumed to be in digits-only form. |
| * @param {string} rawInput |
| * @param {string} nationalPrefix |
| * @param {string} regionCode |
| * @return {boolean} |
| * @private |
| */ |
| - (BOOL)rawInputContainsNationalPrefix:(NSString*)rawInput nationalPrefix:(NSString*)nationalPrefix regionCode:(NSString*)regionCode |
| { |
| NSString *normalizedNationalNumber = [self normalizeDigitsOnly:rawInput]; |
| if ([self isStartingStringByRegex:normalizedNationalNumber regex:nationalPrefix]) |
| { |
| @try { |
| // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the |
| // national prefix when written without it (e.g. 0777123) if we just do |
| // prefix matching. To tackle that, we check the validity of the number if |
| // the assumed national prefix is removed (777123 won't be valid in |
| // Japan). |
| NSString *subString = [normalizedNationalNumber substringFromIndex:nationalPrefix.length]; |
| return [self isValidNumber:[self parse:subString defaultRegion:regionCode]]; |
| } |
| @catch (NSException *ex) { |
| return NO; |
| } |
| } |
| return NO; |
| } |
| |
| |
| /** |
| * Returns NO if a number is from a region whose national significant number |
| * couldn't contain a leading zero, but has the italian_leading_zero field set |
| * to NO. |
| * @param {i18n.phonenumbers.PhoneNumber} number |
| * @return {boolean} |
| * @private |
| */ |
| - (BOOL)hasUnexpectedItalianLeadingZero:(NBPhoneNumber*)number |
| { |
| return number.italianLeadingZero && [self isLeadingZeroPossible:number.countryCode] == NO; |
| } |
| |
| |
| /** |
| * @param {i18n.phonenumbers.PhoneNumber} number |
| * @return {boolean} |
| * @private |
| */ |
| - (BOOL)hasFormattingPatternForNumber:(NBPhoneNumber*)number |
| { |
| NSNumber *countryCallingCode = number.countryCode; |
| NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCallingCode]; |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:phoneNumberRegion]; |
| |
| if (metadata == nil) |
| { |
| return NO; |
| } |
| |
| NSString *nationalNumber = [self getNationalSignificantNumber:number]; |
| NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber]; |
| return formatRule != nil; |
| } |
| |
| |
| /** |
| * Formats a phone number for out-of-country dialing purposes. |
| * |
| * Note that in this version, if the number was entered originally using alpha |
| * characters and this version of the number is stored in raw_input, this |
| * representation of the number will be used rather than the digit |
| * representation. Grouping information, as specified by characters such as '-' |
| * and ' ', will be retained. |
| * |
| * <p><b>Caveats:</b></p> |
| * <ul> |
| * <li>This will not produce good results if the country calling code is both |
| * present in the raw input _and_ is the start of the national number. This is |
| * not a problem in the regions which typically use alpha numbers. |
| * <li>This will also not produce good results if the raw input has any grouping |
| * information within the first three digits of the national number, and if the |
| * function needs to strip preceding digits/words in the raw input before these |
| * digits. Normally people group the first three digits together so this is not |
| * a huge problem - and will be fixed if it proves to be so. |
| * </ul> |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to |
| * be formatted. |
| * @param {string} regionCallingFrom the region where the call is being placed. |
| * @return {string} the formatted phone number. |
| */ |
| - (NSString*)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom error:(NSError **)error |
| { |
| NSString *res = nil; |
| @try { |
| res = [self formatOutOfCountryKeepingAlphaChars:number regionCallingFrom:regionCallingFrom]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NSString*)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber*)number regionCallingFrom:(NSString*)regionCallingFrom |
| { |
| NSString *rawInput = number.rawInput; |
| // If there is no raw input, then we can't keep alpha characters because there |
| // aren't any. In this case, we return formatOutOfCountryCallingNumber. |
| if (rawInput == nil || rawInput.length == 0) |
| { |
| return [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom]; |
| } |
| |
| NSNumber *countryCode = number.countryCode; |
| if ([self hasValidCountryCallingCode:countryCode] == NO) |
| { |
| return rawInput; |
| } |
| // Strip any prefix such as country calling code, IDD, that was present. We do |
| // this by comparing the number in raw_input with the parsed number. To do |
| // this, first we normalize punctuation. We retain number grouping symbols |
| // such as ' ' only. |
| rawInput = [self normalizeHelper:rawInput normalizationReplacements:_ALL_PLUS_NUMBER_GROUPING_SYMBOLS_ removeNonMatches:NO]; |
| //NSLog(@"---- formatOutOfCountryKeepingAlphaChars normalizeHelper rawInput [%@]", rawInput); |
| // Now we trim everything before the first three digits in the parsed number. |
| // We choose three because all valid alpha numbers have 3 digits at the start |
| // - if it does not, then we don't trim anything at all. Similarly, if the |
| // national number was less than three digits, we don't trim anything at all. |
| NSString *nationalNumber = [self getNationalSignificantNumber:number]; |
| if (nationalNumber.length > 3) |
| { |
| NSInteger firstNationalNumberDigit = [self indexOfStringByString:rawInput target:[nationalNumber substringWithRange:NSMakeRange(0, 3)]]; |
| if (firstNationalNumberDigit != -1) |
| { |
| rawInput = [rawInput substringFromIndex:firstNationalNumberDigit]; |
| } |
| } |
| |
| NBPhoneMetaData *metadataForRegionCallingFrom = [self getMetadataForRegion:regionCallingFrom]; |
| if ([countryCode isEqualToNumber: @(NANPA_COUNTRY_CODE_)]) |
| { |
| if ([self isNANPACountry:regionCallingFrom]) |
| { |
| return [NSString stringWithFormat:@"%@ %@", countryCode, rawInput]; |
| } |
| } |
| else if (metadataForRegionCallingFrom != nil && [countryCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCallingFrom]]) |
| { |
| NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:metadataForRegionCallingFrom.numberFormats |
| nationalNumber:nationalNumber]; |
| if (formattingPattern == nil) |
| { |
| // If no pattern above is matched, we format the original input. |
| return rawInput; |
| } |
| |
| NBNumberFormat *newFormat = [formattingPattern copy]; |
| // The first group is the first group of digits that the user wrote |
| // together. |
| newFormat.pattern = @"(\\d+)(.*)"; |
| // Here we just concatenate them back together after the national prefix |
| // has been fixed. |
| newFormat.format = @"$1$2"; |
| // Now we format using this pattern instead of the default pattern, but |
| // with the national prefix prefixed if necessary. |
| // This will not work in the cases where the pattern (and not the leading |
| // digits) decide whether a national prefix needs to be used, since we have |
| // overridden the pattern to match anything, but that is not the case in the |
| // metadata to date. |
| |
| return [self formatNsnUsingPattern:rawInput formattingPattern:newFormat numberFormat:NBEPhoneNumberFormatNATIONAL carrierCode:nil]; |
| } |
| |
| NSString *internationalPrefixForFormatting = @""; |
| // If an unsupported region-calling-from is entered, or a country with |
| // multiple international prefixes, the international format of the number is |
| // returned, unless there is a preferred international prefix. |
| if (metadataForRegionCallingFrom != nil) |
| { |
| NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix; |
| internationalPrefixForFormatting = |
| [self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX_ string:internationalPrefix] ? internationalPrefix : metadataForRegionCallingFrom.preferredInternationalPrefix; |
| } |
| |
| NSString *regionCode = [self getRegionCodeForCountryCode:countryCode]; |
| // Metadata cannot be nil because the country calling code is valid. |
| NBPhoneMetaData *metadataForRegion = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode]; |
| NSString *formattedExtension = [self maybeGetFormattedExtension:number metadata:metadataForRegion numberFormat:NBEPhoneNumberFormatINTERNATIONAL]; |
| if (internationalPrefixForFormatting.length > 0) |
| { |
| return [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCode, rawInput, formattedExtension]; |
| } |
| else |
| { |
| // Invalid region entered as country-calling-from (so no metadata was found |
| // for it) or the region chosen has multiple international dialling |
| // prefixes. |
| return [self prefixNumberWithCountryCallingCode:countryCode phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL formattedNationalNumber:rawInput formattedExtension:formattedExtension]; |
| } |
| } |
| |
| |
| /** |
| * Note in some regions, the national number can be written in two completely |
| * different ways depending on whether it forms part of the NATIONAL format or |
| * INTERNATIONAL format. The numberFormat parameter here is used to specify |
| * which format to use for those cases. If a carrierCode is specified, this will |
| * be inserted into the formatted string to replace $CC. |
| * |
| * @param {string} number a string of characters representing a phone number. |
| * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the |
| * region that we think this number is from. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @param {string=} opt_carrierCode |
| * @return {string} the formatted phone number. |
| * @private |
| */ |
| - (NSString*)formatNsn:(NSString*)phoneNumber metadata:(NBPhoneMetaData*)metadata phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat carrierCode:(NSString*)opt_carrierCode |
| { |
| NSMutableArray *intlNumberFormats = metadata.intlNumberFormats; |
| // When the intlNumberFormats exists, we use that to format national number |
| // for the INTERNATIONAL format instead of using the numberDesc.numberFormats. |
| NSArray *availableFormats = ([intlNumberFormats count] <= 0 || numberFormat == NBEPhoneNumberFormatNATIONAL) ? metadata.numberFormats : intlNumberFormats; |
| NBNumberFormat *formattingPattern = [self chooseFormattingPatternForNumber:availableFormats nationalNumber:phoneNumber]; |
| |
| if (formattingPattern == nil) |
| { |
| return phoneNumber; |
| } |
| |
| return [self formatNsnUsingPattern:phoneNumber formattingPattern:formattingPattern numberFormat:numberFormat carrierCode:opt_carrierCode]; |
| } |
| |
| |
| /** |
| * @param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the |
| * available formats the phone number could be formatted into. |
| * @param {string} nationalNumber a string of characters representing a phone |
| * number. |
| * @return {i18n.phonenumbers.NumberFormat} |
| * @private |
| */ |
| - (NBNumberFormat*)chooseFormattingPatternForNumber:(NSArray*)availableFormats nationalNumber:(NSString*)nationalNumber |
| { |
| for (NBNumberFormat *numFormat in availableFormats) |
| { |
| NSUInteger size = [numFormat.leadingDigitsPatterns count]; |
| // We always use the last leading_digits_pattern, as it is the most detailed. |
| if (size == 0 || [self stringPositionByRegex:nationalNumber regex:[numFormat.leadingDigitsPatterns lastObject]] == 0) |
| { |
| if ([self matchesEntirely:numFormat.pattern string:nationalNumber]) |
| { |
| return numFormat; |
| } |
| } |
| } |
| |
| return nil; |
| } |
| |
| |
| /** |
| * Note that carrierCode is optional - if nil or an empty string, no carrier |
| * code replacement will take place. |
| * |
| * @param {string} nationalNumber a string of characters representing a phone |
| * number. |
| * @param {i18n.phonenumbers.NumberFormat} formattingPattern the formatting rule |
| * the phone number should be formatted into. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @param {string=} opt_carrierCode |
| * @return {string} the formatted phone number. |
| * @private |
| */ |
| - (NSString*)formatNsnUsingPattern:(NSString*)nationalNumber formattingPattern:(NBNumberFormat*)formattingPattern numberFormat:(NBEPhoneNumberFormat)numberFormat carrierCode:(NSString*)opt_carrierCode |
| { |
| NSString *numberFormatRule = formattingPattern.format; |
| NSString *domesticCarrierCodeFormattingRule = formattingPattern.domesticCarrierCodeFormattingRule; |
| NSString *formattedNationalNumber = @""; |
| |
| if (numberFormat == NBEPhoneNumberFormatNATIONAL && [self hasValue:opt_carrierCode] && domesticCarrierCodeFormattingRule.length > 0) |
| { |
| // Replace the $CC in the formatting rule with the desired carrier code. |
| NSString *carrierCodeFormattingRule = [self replaceStringByRegex:domesticCarrierCodeFormattingRule regex:CC_PATTERN_ withTemplate:opt_carrierCode]; |
| // Now replace the $FG in the formatting rule with the first group and |
| // the carrier code combined in the appropriate way. |
| numberFormatRule = [self replaceFirstStringByRegex:numberFormatRule regex:FIRST_GROUP_PATTERN_ |
| withTemplate:carrierCodeFormattingRule]; |
| formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:numberFormatRule]; |
| } |
| else |
| { |
| // Use the national prefix formatting rule instead. |
| NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule; |
| if (numberFormat == NBEPhoneNumberFormatNATIONAL && [self hasValue:nationalPrefixFormattingRule]) |
| { |
| NSString *replacePattern = [self replaceFirstStringByRegex:numberFormatRule regex:FIRST_GROUP_PATTERN_ withTemplate:nationalPrefixFormattingRule]; |
| formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:replacePattern]; |
| } |
| else |
| { |
| formattedNationalNumber = [self replaceStringByRegex:nationalNumber regex:formattingPattern.pattern withTemplate:numberFormatRule]; |
| } |
| } |
| |
| if (numberFormat == NBEPhoneNumberFormatRFC3966) |
| { |
| // Strip any leading punctuation. |
| formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber regex:[NSString stringWithFormat:@"^%@", self.SEPARATOR_PATTERN_] withTemplate:@""]; |
| |
| // Replace the rest with a dash between each number group. |
| formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber regex:self.SEPARATOR_PATTERN_ withTemplate:@"-"]; |
| } |
| return formattedNationalNumber; |
| } |
| |
| |
| /** |
| * Gets a valid number for the specified region. |
| * |
| * @param {string} regionCode the region for which an example number is needed. |
| * @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the |
| * specified region. Returns nil when the metadata does not contain such |
| * information, or the region 001 is passed in. For 001 (representing non- |
| * geographical numbers), call {@link #getExampleNumberForNonGeoEntity} |
| * instead. |
| */ |
| - (NBPhoneNumber*)getExampleNumber:(NSString*)regionCode error:(NSError *__autoreleasing *)error |
| { |
| NBPhoneNumber *res = nil; |
| @try { |
| res = [self getExampleNumber:regionCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NBPhoneNumber*)getExampleNumber:(NSString*)regionCode |
| { |
| return [self getExampleNumberForType:regionCode type:NBEPhoneNumberTypeFIXED_LINE]; |
| } |
| |
| |
| /** |
| * Gets a valid number for the specified region and number type. |
| * |
| * @param {string} regionCode the region for which an example number is needed. |
| * @param {i18n.phonenumbers.PhoneNumberType} type the type of number that is |
| * needed. |
| * @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified |
| * region and type. Returns nil when the metadata does not contain such |
| * information or if an invalid region or region 001 was entered. |
| * For 001 (representing non-geographical numbers), call |
| * {@link #getExampleNumberForNonGeoEntity} instead. |
| */ |
| - (NBPhoneNumber*)getExampleNumberForType:(NSString*)regionCode type:(NBEPhoneNumberType)type error:(NSError *__autoreleasing *)error |
| { |
| NBPhoneNumber *res = nil; |
| @try { |
| res = [self getExampleNumberForType:regionCode type:type]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NBPhoneNumber*)getExampleNumberForType:(NSString*)regionCode type:(NBEPhoneNumberType)type |
| { |
| // Check the region code is valid. |
| if ([self isValidRegionCode:regionCode] == NO) |
| { |
| return nil; |
| } |
| |
| NBPhoneNumberDesc *desc = [self getNumberDescByType:[self getMetadataForRegion:regionCode] type:type]; |
| |
| @try { |
| if ([self hasValue:desc.exampleNumber ]) |
| { |
| return [self parse:desc.exampleNumber defaultRegion:regionCode]; |
| } |
| } |
| @catch (NSException *e) |
| { |
| } |
| |
| return nil; |
| } |
| |
| |
| /** |
| * Gets a valid number for the specified country calling code for a |
| * non-geographical entity. |
| * |
| * @param {number} countryCallingCode the country calling code for a |
| * non-geographical entity. |
| * @return {i18n.phonenumbers.PhoneNumber} a valid number for the |
| * non-geographical entity. Returns nil when the metadata does not contain |
| * such information, or the country calling code passed in does not belong |
| * to a non-geographical entity. |
| */ |
| - (NBPhoneNumber*)getExampleNumberForNonGeoEntity:(NSNumber*)countryCallingCode error:(NSError *__autoreleasing *)error |
| { |
| NBPhoneNumber *res = nil; |
| @try { |
| res = [self getExampleNumberForNonGeoEntity:countryCallingCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (NBPhoneNumber*)getExampleNumberForNonGeoEntity:(NSNumber*)countryCallingCode |
| { |
| NBPhoneMetaData *metadata = [self getMetadataForNonGeographicalRegion:countryCallingCode]; |
| |
| if (metadata != nil) |
| { |
| NBPhoneNumberDesc *desc = metadata.generalDesc; |
| @try { |
| if ([self hasValue:desc.exampleNumber]) |
| { |
| NSString *callCode = [NSString stringWithFormat:@"+%@%@", countryCallingCode, desc.exampleNumber]; |
| return [self parse:callCode defaultRegion:UNKNOWN_REGION_]; |
| } |
| } |
| @catch (NSException *e) { |
| } |
| } |
| return nil; |
| } |
| |
| |
| /** |
| * Gets the formatted extension of a phone number, if the phone number had an |
| * extension specified. If not, it returns an empty string. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have |
| * an extension. |
| * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the |
| * region that we think this number is from. |
| * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the |
| * phone number should be formatted into. |
| * @return {string} the formatted extension if any. |
| * @private |
| */ |
| - (NSString*)maybeGetFormattedExtension:(NBPhoneNumber*)number metadata:(NBPhoneMetaData*)metadata numberFormat:(NBEPhoneNumberFormat)numberFormat |
| { |
| if ([self hasValue:number.extension] == NO) |
| { |
| return @""; |
| } |
| else |
| { |
| if (numberFormat == NBEPhoneNumberFormatRFC3966) |
| { |
| return [NSString stringWithFormat:@"%@%@", RFC3966_EXTN_PREFIX_, number.extension]; |
| } |
| else |
| { |
| if ([self hasValue:metadata.preferredExtnPrefix]) |
| { |
| return [NSString stringWithFormat:@"%@%@", metadata.preferredExtnPrefix, number.extension]; |
| } |
| else |
| { |
| return [NSString stringWithFormat:@"%@%@", DEFAULT_EXTN_PREFIX_, number.extension]; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * @param {i18n.phonenumbers.PhoneMetadata} metadata |
| * @param {i18n.phonenumbers.PhoneNumberType} type |
| * @return {i18n.phonenumbers.PhoneNumberDesc} |
| * @private |
| */ |
| - (NBPhoneNumberDesc*)getNumberDescByType:(NBPhoneMetaData*)metadata type:(NBEPhoneNumberType)type |
| { |
| switch (type) |
| { |
| case NBEPhoneNumberTypePREMIUM_RATE: |
| return metadata.premiumRate; |
| case NBEPhoneNumberTypeTOLL_FREE: |
| return metadata.tollFree; |
| case NBEPhoneNumberTypeMOBILE: |
| if (metadata.mobile == nil) return metadata.generalDesc; |
| return metadata.mobile; |
| case NBEPhoneNumberTypeFIXED_LINE: |
| case NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE: |
| if (metadata.fixedLine == nil) return metadata.generalDesc; |
| return metadata.fixedLine; |
| case NBEPhoneNumberTypeSHARED_COST: |
| return metadata.sharedCost; |
| case NBEPhoneNumberTypeVOIP: |
| return metadata.voip; |
| case NBEPhoneNumberTypePERSONAL_NUMBER: |
| return metadata.personalNumber; |
| case NBEPhoneNumberTypePAGER: |
| return metadata.pager; |
| case NBEPhoneNumberTypeUAN: |
| return metadata.uan; |
| case NBEPhoneNumberTypeVOICEMAIL: |
| return metadata.voicemail; |
| default: |
| return metadata.generalDesc; |
| } |
| } |
| |
| /** |
| * Gets the type of a phone number. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want |
| * to know the type. |
| * @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number. |
| */ |
| - (NBEPhoneNumberType)getNumberType:(NBPhoneNumber*)phoneNumber |
| { |
| NSString *regionCode = [self getRegionCodeForNumber:phoneNumber]; |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:phoneNumber.countryCode regionCode:regionCode]; |
| if (metadata == nil) |
| { |
| return NBEPhoneNumberTypeUNKNOWN; |
| } |
| |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber]; |
| return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata]; |
| } |
| |
| |
| /** |
| * @param {string} nationalNumber |
| * @param {i18n.phonenumbers.PhoneMetadata} metadata |
| * @return {i18n.phonenumbers.PhoneNumberType} |
| * @private |
| */ |
| - (NBEPhoneNumberType)getNumberTypeHelper:(NSString*)nationalNumber metadata:(NBPhoneMetaData*)metadata |
| { |
| NBPhoneNumberDesc *generalNumberDesc = metadata.generalDesc; |
| |
| //NSLog(@"getNumberTypeHelper - UNKNOWN 1"); |
| if ([self hasValue:generalNumberDesc.nationalNumberPattern] == NO || |
| [self isNumberMatchingDesc:nationalNumber numberDesc:generalNumberDesc] == NO) |
| { |
| //NSLog(@"getNumberTypeHelper - UNKNOWN 2"); |
| return NBEPhoneNumberTypeUNKNOWN; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - PREMIUM_RATE 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.premiumRate]) |
| { |
| //NSLog(@"getNumberTypeHelper - PREMIUM_RATE 2"); |
| return NBEPhoneNumberTypePREMIUM_RATE; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - TOLL_FREE 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.tollFree]) |
| { |
| //NSLog(@"getNumberTypeHelper - TOLL_FREE 2"); |
| return NBEPhoneNumberTypeTOLL_FREE; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - SHARED_COST 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.sharedCost]) |
| { |
| //NSLog(@"getNumberTypeHelper - SHARED_COST 2"); |
| return NBEPhoneNumberTypeSHARED_COST; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - VOIP 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voip]) |
| { |
| //NSLog(@"getNumberTypeHelper - VOIP 2"); |
| return NBEPhoneNumberTypeVOIP; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - PERSONAL_NUMBER 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.personalNumber]) |
| { |
| //NSLog(@"getNumberTypeHelper - PERSONAL_NUMBER 2"); |
| return NBEPhoneNumberTypePERSONAL_NUMBER; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - PAGER 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.pager]) |
| { |
| //NSLog(@"getNumberTypeHelper - PAGER 2"); |
| return NBEPhoneNumberTypePAGER; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - UAN 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.uan]) |
| { |
| //NSLog(@"getNumberTypeHelper - UAN 2"); |
| return NBEPhoneNumberTypeUAN; |
| } |
| |
| //NSLog(@"getNumberTypeHelper - VOICEMAIL 1"); |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voicemail]) |
| { |
| //NSLog(@"getNumberTypeHelper - VOICEMAIL 2"); |
| return NBEPhoneNumberTypeVOICEMAIL; |
| } |
| |
| if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.fixedLine]) |
| { |
| if (metadata.sameMobileAndFixedLinePattern) |
| { |
| //NSLog(@"getNumberTypeHelper - FIXED_LINE_OR_MOBILE"); |
| return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE; |
| } |
| else if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) |
| { |
| //NSLog(@"getNumberTypeHelper - FIXED_LINE_OR_MOBILE"); |
| return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE; |
| } |
| //NSLog(@"getNumberTypeHelper - FIXED_LINE"); |
| return NBEPhoneNumberTypeFIXED_LINE; |
| } |
| |
| // Otherwise, test to see if the number is mobile. Only do this if certain |
| // that the patterns for mobile and fixed line aren't the same. |
| if ([metadata sameMobileAndFixedLinePattern] == NO && [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) |
| { |
| return NBEPhoneNumberTypeMOBILE; |
| } |
| |
| return NBEPhoneNumberTypeUNKNOWN; |
| } |
| |
| |
| /** |
| * Returns the metadata for the given region code or {@code nil} if the region |
| * code is invalid or unknown. |
| * |
| * @param {?string} regionCode |
| * @return {i18n.phonenumbers.PhoneMetadata} |
| */ |
| - (NBPhoneMetaData*)getMetadataForRegion:(NSString*)regionCode |
| { |
| if ([self hasValue:regionCode] == NO) |
| { |
| return nil; |
| } |
| |
| regionCode = [regionCode uppercaseString]; |
| |
| NBPhoneMetaData *metadata = [self.coreMetaData objectForKey:regionCode]; |
| |
| return metadata; |
| } |
| |
| |
| /** |
| * @param {number} countryCallingCode |
| * @return {i18n.phonenumbers.PhoneMetadata} |
| */ |
| - (NBPhoneMetaData*)getMetadataForNonGeographicalRegion:(NSNumber*)countryCallingCode |
| { |
| NSString *countryCallingCodeStr = [NSString stringWithFormat:@"%@", countryCallingCode]; |
| return [self getMetadataForRegion:countryCallingCodeStr]; |
| } |
| |
| |
| /** |
| * @param {string} nationalNumber |
| * @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc |
| * @return {boolean} |
| * @private |
| */ |
| - (BOOL)isNumberMatchingDesc:(NSString*)nationalNumber numberDesc:(NBPhoneNumberDesc*)numberDesc |
| { |
| if (numberDesc == nil) |
| return NO; |
| |
| if ([self hasValue:numberDesc.possibleNumberPattern] == NO || [numberDesc.possibleNumberPattern isEqual:@"NA"]) |
| return [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber]; |
| |
| if ([self hasValue:numberDesc.nationalNumberPattern] == NO || [numberDesc.nationalNumberPattern isEqual:@"NA"]) |
| return [self matchesEntirely:numberDesc.possibleNumberPattern string:nationalNumber]; |
| |
| return [self matchesEntirely:numberDesc.possibleNumberPattern string:nationalNumber] && |
| [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber]; |
| } |
| |
| |
| /** |
| * Tests whether a phone number matches a valid pattern. Note this doesn't |
| * verify the number is actually in use, which is impossible to tell by just |
| * looking at a number itself. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want |
| * to validate. |
| * @return {boolean} a boolean that indicates whether the number is of a valid |
| * pattern. |
| */ |
| - (BOOL)isValidNumber:(NBPhoneNumber*)number |
| { |
| NSString *regionCode = [self getRegionCodeForNumber:number]; |
| return [self isValidNumberForRegion:number regionCode:regionCode]; |
| } |
| |
| |
| /** |
| * Tests whether a phone number is valid for a certain region. Note this doesn't |
| * verify the number is actually in use, which is impossible to tell by just |
| * looking at a number itself. If the country calling code is not the same as |
| * the country calling code for the region, this immediately exits with NO. |
| * After this, the specific number pattern rules for the region are examined. |
| * This is useful for determining for example whether a particular number is |
| * valid for Canada, rather than just a valid NANPA number. |
| * Warning: In most cases, you want to use {@link #isValidNumber} instead. For |
| * example, this method will mark numbers from British Crown dependencies such |
| * as the Isle of Man as invalid for the region "GB" (United Kingdom), since it |
| * has its own region code, "IM", which may be undesirable. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want |
| * to validate. |
| * @param {?string} regionCode the region that we want to validate the phone |
| * number for. |
| * @return {boolean} a boolean that indicates whether the number is of a valid |
| * pattern. |
| */ |
| - (BOOL)isValidNumberForRegion:(NBPhoneNumber*)number regionCode:(NSString*)regionCode |
| { |
| NSNumber *countryCode = number.countryCode; |
| |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode]; |
| if (metadata == nil || |
| ([_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] == NO && |
| ![countryCode isEqualToNumber: [self getCountryCodeForValidRegion:regionCode]])) |
| { |
| // Either the region code was invalid, or the country calling code for this |
| // number does not match that of the region code. |
| return NO; |
| } |
| |
| NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc; |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number]; |
| |
| // For regions where we don't have metadata for PhoneNumberDesc, we treat any |
| // number passed in as a valid number if its national significant number is |
| // between the minimum and maximum lengths defined by ITU for a national |
| // significant number. |
| if ([self hasValue:generalNumDesc.nationalNumberPattern] == NO) |
| { |
| NSUInteger numberLength = nationalSignificantNumber.length; |
| return numberLength > MIN_LENGTH_FOR_NSN_ && numberLength <= MAX_LENGTH_FOR_NSN_; |
| } |
| |
| return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata] != NBEPhoneNumberTypeUNKNOWN; |
| } |
| |
| |
| /** |
| * Returns the region where a phone number is from. This could be used for |
| * geocoding at the region level. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin |
| * we want to know. |
| * @return {?string} the region where the phone number is from, or nil |
| * if no region matches this calling code. |
| */ |
| - (NSString*)getRegionCodeForNumber:(NBPhoneNumber*)phoneNumber |
| { |
| if (phoneNumber == nil) |
| { |
| return nil; |
| } |
| |
| NSArray *regionCodes = [self regionCodeFromCountryCode:phoneNumber.countryCode]; |
| if (regionCodes == nil || [regionCodes count] <= 0) |
| { |
| return nil; |
| } |
| |
| if ([regionCodes count] == 1) |
| { |
| return [regionCodes objectAtIndex:0]; |
| } |
| else |
| { |
| return [self getRegionCodeForNumberFromRegionList:phoneNumber regionCodes:regionCodes]; |
| } |
| } |
| |
| |
| /** |
| * @param {i18n.phonenumbers.PhoneNumber} number |
| * @param {Array.<string>} regionCodes |
| * @return {?string} |
| * @private |
| |
| */ |
| - (NSString*)getRegionCodeForNumberFromRegionList:(NBPhoneNumber*)phoneNumber regionCodes:(NSArray*)regionCodes |
| { |
| NSString *nationalNumber = [self getNationalSignificantNumber:phoneNumber]; |
| NSUInteger regionCodesCount = [regionCodes count]; |
| |
| for (NSUInteger i = 0; i<regionCodesCount; i++) |
| { |
| NSString *regionCode = [regionCodes objectAtIndex:i]; |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode]; |
| |
| if ([self hasValue:metadata.leadingDigits]) |
| { |
| if ([self stringPositionByRegex:nationalNumber regex:metadata.leadingDigits] == 0) |
| { |
| return regionCode; |
| } |
| } |
| else if ([self getNumberTypeHelper:nationalNumber metadata:metadata] != NBEPhoneNumberTypeUNKNOWN) |
| { |
| return regionCode; |
| } |
| } |
| |
| return nil; |
| } |
| |
| |
| /** |
| * Returns the region code that matches the specific country calling code. In |
| * the case of no region code being found, ZZ will be returned. In the case of |
| * multiple regions, the one designated in the metadata as the 'main' region for |
| * this calling code will be returned. |
| * |
| * @param {number} countryCallingCode the country calling code. |
| * @return {string} |
| */ |
| - (NSString*)getRegionCodeForCountryCode:(NSNumber*)countryCallingCode |
| { |
| NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode]; |
| return regionCodes == nil ? UNKNOWN_REGION_ : [regionCodes objectAtIndex:0]; |
| } |
| |
| |
| /** |
| * Returns a list with the region codes that match the specific country calling |
| * code. For non-geographical country calling codes, the region code 001 is |
| * returned. Also, in the case of no region code being found, an empty list is |
| * returned. |
| * |
| * @param {number} countryCallingCode the country calling code. |
| * @return {Array.<string>} |
| */ |
| - (NSArray*)getRegionCodesForCountryCode:(NSNumber*)countryCallingCode |
| { |
| NSArray *regionCodes = [self regionCodeFromCountryCode:countryCallingCode]; |
| return regionCodes == nil ? nil : regionCodes; |
| } |
| |
| |
| /** |
| * Returns the country calling code for a specific region. For example, this |
| * would be 1 for the United States, and 64 for New Zealand. |
| * |
| * @param {?string} regionCode the region that we want to get the country |
| * calling code for. |
| * @return {number} the country calling code for the region denoted by |
| * regionCode. |
| */ |
| - (NSNumber*)getCountryCodeForRegion:(NSString*)regionCode |
| { |
| if ([self isValidRegionCode:regionCode] == NO) |
| { |
| return @0; |
| } |
| |
| NSError *error = nil; |
| NSNumber *res = [self getCountryCodeForValidRegion:regionCode error:&error]; |
| if (error != nil) |
| return @0; |
| return res; |
| } |
| |
| |
| /** |
| * Returns the country calling code for a specific region. For example, this |
| * would be 1 for the United States, and 64 for New Zealand. Assumes the region |
| * is already valid. |
| * |
| * @param {?string} regionCode the region that we want to get the country |
| * calling code for. |
| * @return {number} the country calling code for the region denoted by |
| * regionCode. |
| * @throws {string} if the region is invalid |
| * @private |
| */ |
| - (NSNumber *)getCountryCodeForValidRegion:(NSString*)regionCode error:(NSError**)error |
| { |
| NSNumber * res = nil; |
| @try { |
| res = [self getCountryCodeForValidRegion:regionCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NSNumber *)getCountryCodeForValidRegion:(NSString*)regionCode |
| { |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode]; |
| if (metadata == nil) |
| { |
| NSException* metaException = [NSException exceptionWithName:@"INVALID_REGION_CODE" |
| reason:[NSString stringWithFormat:@"Invalid region code:%@", regionCode] |
| userInfo:nil]; |
| @throw metaException; |
| } |
| return metadata.countryCode; |
| } |
| |
| |
| /** |
| * Returns the national dialling prefix for a specific region. For example, this |
| * would be 1 for the United States, and 0 for New Zealand. Set stripNonDigits |
| * to NO to strip symbols like '~' (which indicates a wait for a dialling |
| * tone) from the prefix returned. If no national prefix is present, we return |
| * nil. |
| * |
| * <p>Warning: Do not use this method for do-your-own formatting - for some |
| * regions, the national dialling prefix is used only for certain types of |
| * numbers. Use the library's formatting functions to prefix the national prefix |
| * when required. |
| * |
| * @param {?string} regionCode the region that we want to get the dialling |
| * prefix for. |
| * @param {boolean} stripNonDigits NO to strip non-digits from the national |
| * dialling prefix. |
| * @return {?string} the dialling prefix for the region denoted by |
| * regionCode. |
| */ |
| - (NSString*)getNddPrefixForRegion:(NSString*)regionCode stripNonDigits:(BOOL)stripNonDigits |
| { |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:regionCode]; |
| if (metadata == nil) |
| { |
| return nil; |
| } |
| |
| NSString *nationalPrefix = metadata.nationalPrefix; |
| // If no national prefix was found, we return nil. |
| if (nationalPrefix.length == 0) |
| { |
| return nil; |
| } |
| |
| if (stripNonDigits) |
| { |
| // Note: if any other non-numeric symbols are ever used in national |
| // prefixes, these would have to be removed here as well. |
| nationalPrefix = [nationalPrefix stringByReplacingOccurrencesOfString:@"~" withString:@""]; |
| } |
| return nationalPrefix; |
| } |
| |
| |
| /** |
| * Checks if this is a region under the North American Numbering Plan |
| * Administration (NANPA). |
| * |
| * @param {?string} regionCode the ISO 3166-1 two-letter region code. |
| * @return {boolean} NO if regionCode is one of the regions under NANPA. |
| */ |
| - (BOOL)isNANPACountry:(NSString*)regionCode |
| { |
| BOOL isExists = NO; |
| |
| NSArray *res = [self regionCodeFromCountryCode:@(NANPA_COUNTRY_CODE_)]; |
| for (NSString *inRegionCode in res) |
| { |
| if ([inRegionCode isEqualToString:regionCode.uppercaseString]) |
| { |
| isExists = YES; |
| } |
| } |
| |
| return regionCode != nil && isExists; |
| } |
| |
| |
| /** |
| * Checks whether countryCode represents the country calling code from a region |
| * whose national significant number could contain a leading zero. An example of |
| * such a region is Italy. Returns NO if no metadata for the country is |
| * found. |
| * |
| * @param {number} countryCallingCode the country calling code. |
| * @return {boolean} |
| */ |
| - (BOOL)isLeadingZeroPossible:(NSNumber*)countryCallingCode |
| { |
| NBPhoneMetaData *mainMetadataForCallingCode = [self getMetadataForRegionOrCallingCode:countryCallingCode |
| regionCode:[self getRegionCodeForCountryCode:countryCallingCode]]; |
| |
| return mainMetadataForCallingCode != nil && mainMetadataForCallingCode.leadingZeroPossible; |
| } |
| |
| |
| /** |
| * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. |
| * A valid vanity number will start with at least 3 digits and will have three |
| * or more alpha characters. This does not do region-specific checks - to work |
| * out if this number is actually valid for a region, it should be parsed and |
| * methods such as {@link #isPossibleNumberWithReason} and |
| * {@link #isValidNumber} should be used. |
| * |
| * @param {string} number the number that needs to be checked. |
| * @return {boolean} NO if the number is a valid vanity number. |
| */ |
| - (BOOL)isAlphaNumber:(NSString*)number |
| { |
| if ([self isViablePhoneNumber:number] == NO) |
| { |
| // Number is too short, or doesn't match the basic phone number pattern. |
| return NO; |
| } |
| |
| /** @type {!goog.string.StringBuffer} */ |
| NSString *strippedNumber = [number copy]; |
| [self maybeStripExtension:&strippedNumber]; |
| |
| return [self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:strippedNumber]; |
| } |
| |
| |
| /** |
| * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of |
| * returning the reason for failure, this method returns a boolean value. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be |
| * checked. |
| * @return {boolean} NO if the number is possible. |
| */ |
| - (BOOL)isPossibleNumber:(NBPhoneNumber*)number error:(NSError**)error |
| { |
| BOOL res = NO; |
| @try { |
| res = [self isPossibleNumber:number]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (BOOL)isPossibleNumber:(NBPhoneNumber*)number |
| { |
| return [self isPossibleNumberWithReason:number] == NBEValidationResultIS_POSSIBLE; |
| } |
| |
| |
| /** |
| * Helper method to check a number against a particular pattern and determine |
| * whether it matches, or is too short or too long. Currently, if a number |
| * pattern suggests that numbers of length 7 and 10 are possible, and a number |
| * in between these possible lengths is entered, such as of length 8, this will |
| * return TOO_LONG. |
| * |
| * @param {string} numberPattern |
| * @param {string} number |
| * @return {ValidationResult} |
| * @private |
| */ |
| - (NBEValidationResult)testNumberLengthAgainstPattern:(NSString*)numberPattern number:(NSString*)number |
| { |
| if ([self matchesEntirely:numberPattern string:number]) |
| { |
| return NBEValidationResultIS_POSSIBLE; |
| } |
| |
| if ([self stringPositionByRegex:number regex:numberPattern] == 0) |
| { |
| return NBEValidationResultTOO_LONG; |
| } |
| else |
| { |
| return NBEValidationResultTOO_SHORT; |
| } |
| } |
| |
| |
| /** |
| * Check whether a phone number is a possible number. It provides a more lenient |
| * check than {@link #isValidNumber} in the following sense: |
| * <ol> |
| * <li>It only checks the length of phone numbers. In particular, it doesn't |
| * check starting digits of the number. |
| * <li>It doesn't attempt to figure out the type of the number, but uses general |
| * rules which applies to all types of phone numbers in a region. Therefore, it |
| * is much faster than isValidNumber. |
| * <li>For fixed line numbers, many regions have the concept of area code, which |
| * together with subscriber number constitute the national significant number. |
| * It is sometimes okay to dial the subscriber number only when dialing in the |
| * same area. This function will return NO if the subscriber-number-only |
| * version is passed in. On the other hand, because isValidNumber validates |
| * using information on both starting digits (for fixed line numbers, that would |
| * most likely be area codes) and length (obviously includes the length of area |
| * codes for fixed line numbers), it will return NO for the |
| * subscriber-number-only version. |
| * </ol> |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be |
| * checked. |
| * @return {ValidationResult} a |
| * ValidationResult object which indicates whether the number is possible. |
| */ |
| - (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber*)number error:(NSError *__autoreleasing *)error |
| { |
| NBEValidationResult res = -1; |
| @try { |
| res = [self isPossibleNumberWithReason:number]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return res; |
| } |
| |
| |
| - (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber*)number |
| { |
| NSString *nationalNumber = [self getNationalSignificantNumber:number]; |
| NSNumber *countryCode = number.countryCode; |
| // Note: For Russian Fed and NANPA numbers, we just use the rules from the |
| // default region (US or Russia) since the getRegionCodeForNumber will not |
| // work if the number is possible but not valid. This would need to be |
| // revisited if the possible number pattern ever differed between various |
| // regions within those plans. |
| if ([self hasValidCountryCallingCode:countryCode] == NO) |
| { |
| return NBEValidationResultINVALID_COUNTRY_CODE; |
| } |
| |
| NSString *regionCode = [self getRegionCodeForCountryCode:countryCode]; |
| // Metadata cannot be nil because the country calling code is valid. |
| NBPhoneMetaData *metadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode]; |
| NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc; |
| |
| // Handling case of numbers with no metadata. |
| if ([self hasValue:generalNumDesc.nationalNumberPattern] == NO) |
| { |
| NSUInteger numberLength = nationalNumber.length; |
| |
| if (numberLength < MIN_LENGTH_FOR_NSN_) |
| { |
| return NBEValidationResultTOO_SHORT; |
| } |
| else if (numberLength > MAX_LENGTH_FOR_NSN_) |
| { |
| return NBEValidationResultTOO_LONG; |
| } |
| else |
| { |
| return NBEValidationResultIS_POSSIBLE; |
| } |
| } |
| |
| NSString *possibleNumberPattern = generalNumDesc.possibleNumberPattern; |
| return [self testNumberLengthAgainstPattern:possibleNumberPattern number:nationalNumber]; |
| } |
| |
| |
| /** |
| * Check whether a phone number is a possible number given a number in the form |
| * of a string, and the region where the number could be dialed from. It |
| * provides a more lenient check than {@link #isValidNumber}. See |
| * {@link #isPossibleNumber} for details. |
| * |
| * <p>This method first parses the number, then invokes |
| * {@link #isPossibleNumber} with the resultant PhoneNumber object. |
| * |
| * @param {string} number the number that needs to be checked, in the form of a |
| * string. |
| * @param {string} regionDialingFrom the region that we are expecting the number |
| * to be dialed from. |
| * Note this is different from the region where the number belongs. |
| * For example, the number +1 650 253 0000 is a number that belongs to US. |
| * When written in this form, it can be dialed from any region. When it is |
| * written as 00 1 650 253 0000, it can be dialed from any region which uses |
| * an international dialling prefix of 00. When it is written as |
| * 650 253 0000, it can only be dialed from within the US, and when written |
| * as 253 0000, it can only be dialed from within a smaller area in the US |
| * (Mountain View, CA, to be more specific). |
| * @return {boolean} NO if the number is possible. |
| */ |
| - (BOOL)isPossibleNumberString:(NSString*)number regionDialingFrom:(NSString*)regionDialingFrom error:(NSError**)error |
| { |
| BOOL res = NO; |
| @try { |
| res = [self isPossibleNumberString:number regionDialingFrom:regionDialingFrom]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (BOOL)isPossibleNumberString:(NSString*)number regionDialingFrom:(NSString*)regionDialingFrom |
| { |
| @try { |
| return [self isPossibleNumber:[self parse:number defaultRegion:regionDialingFrom]]; |
| } |
| @catch (NSException *e) { |
| return NO; |
| } |
| } |
| |
| |
| /** |
| * Attempts to extract a valid number from a phone number that is too long to be |
| * valid, and resets the PhoneNumber object passed in to that valid version. If |
| * no valid number could be extracted, the PhoneNumber object passed in will not |
| * be modified. |
| * @param {i18n.phonenumbers.PhoneNumber} number a PhoneNumber object which |
| * contains a number that is too long to be valid. |
| * @return {boolean} NO if a valid phone number can be successfully extracted. |
| */ |
| - (BOOL)truncateTooLongNumber:(NBPhoneNumber*)number error:(NSError**)error |
| { |
| BOOL res = NO; |
| @try { |
| res = [self truncateTooLongNumber:number]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (BOOL)truncateTooLongNumber:(NBPhoneNumber*)number |
| { |
| if ([self isValidNumber:number]) |
| { |
| return YES; |
| } |
| |
| NBPhoneNumber *numberCopy = [number copy]; |
| NSNumber *nationalNumber = number.nationalNumber; |
| do { |
| nationalNumber = [NSNumber numberWithLongLong:(long long)floor(nationalNumber.unsignedLongLongValue / 10)]; |
| numberCopy.nationalNumber = [nationalNumber copy]; |
| if ([nationalNumber isEqualToNumber:@0] || [self isPossibleNumberWithReason:numberCopy] == NBEValidationResultTOO_SHORT) { |
| return NO; |
| } |
| } |
| while ([self isValidNumber:numberCopy] == NO); |
| |
| number.nationalNumber = nationalNumber; |
| return YES; |
| } |
| |
| |
| /** |
| * Extracts country calling code from fullNumber, returns it and places the |
| * remaining number in nationalNumber. It assumes that the leading plus sign or |
| * IDD has already been removed. Returns 0 if fullNumber doesn't start with a |
| * valid country calling code, and leaves nationalNumber unmodified. |
| * |
| * @param {!goog.string.StringBuffer} fullNumber |
| * @param {!goog.string.StringBuffer} nationalNumber |
| * @return {number} |
| */ |
| - (NSNumber*)extractCountryCode:(NSString*)fullNumber nationalNumber:(NSString**)nationalNumber |
| { |
| if ((fullNumber.length == 0) || ([[fullNumber substringToIndex:1] isEqualToString:@"0"])) |
| { |
| // Country codes do not begin with a '0'. |
| return @0; |
| } |
| |
| NSUInteger numberLength = fullNumber.length; |
| |
| for (NSUInteger i = 1; i <= MAX_LENGTH_COUNTRY_CODE_ && i <= numberLength; ++i) |
| { |
| NSString *subNumber = [fullNumber substringWithRange:NSMakeRange(0, i)]; |
| NSNumber *potentialCountryCode = [NSNumber numberWithInteger:[subNumber integerValue]]; |
| |
| NSArray *regionCodes = [self regionCodeFromCountryCode:potentialCountryCode]; |
| if (regionCodes != nil && regionCodes.count > 0) |
| { |
| if (nationalNumber != NULL) |
| { |
| if ((*nationalNumber) == nil) |
| (*nationalNumber) = [NSString stringWithFormat:@"%@", [fullNumber substringFromIndex:i]]; |
| else |
| (*nationalNumber) = [NSString stringWithFormat:@"%@%@", (*nationalNumber), [fullNumber substringFromIndex:i]]; |
| } |
| return potentialCountryCode; |
| } |
| } |
| |
| return @0; |
| } |
| |
| |
| /** |
| * Tries to extract a country calling code from a number. This method will |
| * return zero if no country calling code is considered to be present. Country |
| * calling codes are extracted in the following ways: |
| * <ul> |
| * <li>by stripping the international dialing prefix of the region the person is |
| * dialing from, if this is present in the number, and looking at the next |
| * digits |
| * <li>by stripping the '+' sign if present and then looking at the next digits |
| * <li>by comparing the start of the number and the country calling code of the |
| * default region. If the number is not considered possible for the numbering |
| * plan of the default region initially, but starts with the country calling |
| * code of this region, validation will be reattempted after stripping this |
| * country calling code. If this number is considered a possible number, then |
| * the first digits will be considered the country calling code and removed as |
| * such. |
| * </ul> |
| * |
| * It will throw a i18n.phonenumbers.Error if the number starts with a '+' but |
| * the country calling code supplied after this does not match that of any known |
| * region. |
| * |
| * @param {string} number non-normalized telephone number that we wish to |
| * extract a country calling code from - may begin with '+'. |
| * @param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata |
| * about the region this number may be from. |
| * @param {!goog.string.StringBuffer} nationalNumber a string buffer to store |
| * the national significant number in, in the case that a country calling |
| * code was extracted. The number is appended to any existing contents. If |
| * no country calling code was extracted, this will be left unchanged. |
| * @param {boolean} keepRawInput NO if the country_code_source and |
| * preferred_carrier_code fields of phoneNumber should be populated. |
| * @param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object |
| * where the country_code and country_code_source need to be populated. |
| * Note the country_code is always populated, whereas country_code_source is |
| * only populated when keepCountryCodeSource is NO. |
| * @return {number} the country calling code extracted or 0 if none could be |
| * extracted. |
| * @throws {i18n.phonenumbers.Error} |
| */ |
| - (NSNumber*)maybeExtractCountryCode:(NSString*)number metadata:(NBPhoneMetaData*)defaultRegionMetadata |
| nationalNumber:(NSString**)nationalNumber keepRawInput:(BOOL)keepRawInput |
| phoneNumber:(NBPhoneNumber**)phoneNumber error:(NSError**)error |
| { |
| if (nationalNumber == NULL || phoneNumber == NULL) |
| { |
| return @0; |
| } |
| |
| NSNumber *res = @0; |
| @try { |
| res = [self maybeExtractCountryCode:number metadata:defaultRegionMetadata |
| nationalNumber:nationalNumber keepRawInput:keepRawInput phoneNumber:phoneNumber]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return res; |
| } |
| |
| - (NSNumber*)maybeExtractCountryCode:(NSString*)number metadata:(NBPhoneMetaData*)defaultRegionMetadata |
| nationalNumber:(NSString**)nationalNumber keepRawInput:(BOOL)keepRawInput |
| phoneNumber:(NBPhoneNumber**)phoneNumber |
| { |
| if (nationalNumber == NULL || phoneNumber == NULL || number.length <= 0) |
| { |
| return @0; |
| } |
| |
| NSString *fullNumber = [number copy]; |
| // Set the default prefix to be something that will never match. |
| NSString *possibleCountryIddPrefix = @""; |
| if (defaultRegionMetadata != nil) |
| { |
| possibleCountryIddPrefix = defaultRegionMetadata.internationalPrefix; |
| } |
| |
| if (possibleCountryIddPrefix == nil) |
| { |
| possibleCountryIddPrefix = @"NonMatch"; |
| } |
| |
| /** @type {i18n.phonenumbers.PhoneNumber.CountryCodeSource} */ |
| NBECountryCodeSource countryCodeSource = [self maybeStripInternationalPrefixAndNormalize:&fullNumber |
| possibleIddPrefix:possibleCountryIddPrefix]; |
| |
| if (keepRawInput) |
| { |
| (*phoneNumber).countryCodeSource = [NSNumber numberWithInt:countryCodeSource]; |
| } |
| |
| if (countryCodeSource != NBECountryCodeSourceFROM_DEFAULT_COUNTRY) |
| { |
| if (fullNumber.length <= MIN_LENGTH_FOR_NSN_) |
| { |
| @throw [NSException exceptionWithName:@"TOO_SHORT_AFTER_IDD" |
| reason:[NSString stringWithFormat:@"TOO_SHORT_AFTER_IDD:%@", fullNumber] |
| userInfo:nil]; |
| } |
| |
| NSNumber *potentialCountryCode = [self extractCountryCode:fullNumber nationalNumber:nationalNumber]; |
| |
| if (![potentialCountryCode isEqualToNumber:@0]) |
| { |
| (*phoneNumber).countryCode = potentialCountryCode; |
| return potentialCountryCode; |
| } |
| |
| // If this fails, they must be using a strange country calling code that we |
| // don't recognize, or that doesn't exist. |
| @throw [NSException exceptionWithName:@"INVALID_COUNTRY_CODE" |
| reason:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", potentialCountryCode] |
| userInfo:nil]; |
| } |
| else if (defaultRegionMetadata != nil) |
| { |
| // Check to see if the number starts with the country calling code for the |
| // default region. If so, we remove the country calling code, and do some |
| // checks on the validity of the number before and after. |
| NSNumber *defaultCountryCode = defaultRegionMetadata.countryCode; |
| NSString *defaultCountryCodeString = [NSString stringWithFormat:@"%@", defaultCountryCode]; |
| NSString *normalizedNumber = [fullNumber copy]; |
| |
| if ([normalizedNumber hasPrefix:defaultCountryCodeString]) |
| { |
| NSString *potentialNationalNumber = [normalizedNumber substringFromIndex:defaultCountryCodeString.length]; |
| NBPhoneNumberDesc *generalDesc = defaultRegionMetadata.generalDesc; |
| |
| NSString *validNumberPattern = generalDesc.nationalNumberPattern; |
| // Passing null since we don't need the carrier code. |
| [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber metadata:defaultRegionMetadata carrierCode:nil]; |
| |
| NSString *potentialNationalNumberStr = [potentialNationalNumber copy]; |
| NSString *possibleNumberPattern = generalDesc.possibleNumberPattern; |
| // If the number was not valid before but is valid now, or if it was too |
| // long before, we consider the number with the country calling code |
| // stripped to be a better result and keep that instead. |
| if ((![self matchesEntirely:validNumberPattern string:fullNumber] && |
| [self matchesEntirely:validNumberPattern string:potentialNationalNumberStr]) || |
| [self testNumberLengthAgainstPattern:possibleNumberPattern number:fullNumber] == NBEValidationResultTOO_LONG) |
| { |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:potentialNationalNumberStr]; |
| if (keepRawInput) |
| { |
| (*phoneNumber).countryCodeSource = [NSNumber numberWithInt:NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN]; |
| } |
| (*phoneNumber).countryCode = defaultCountryCode; |
| return defaultCountryCode; |
| } |
| } |
| } |
| // No country calling code present. |
| (*phoneNumber).countryCode = @0; |
| return @0; |
| } |
| |
| |
| /** |
| * Strips the IDD from the start of the number if present. Helper function used |
| * by maybeStripInternationalPrefixAndNormalize. |
| * |
| * @param {!RegExp} iddPattern the regular expression for the international |
| * prefix. |
| * @param {!goog.string.StringBuffer} number the phone number that we wish to |
| * strip any international dialing prefix from. |
| * @return {boolean} NO if an international prefix was present. |
| * @private |
| */ |
| - (BOOL)parsePrefixAsIdd:(NSString*)iddPattern sourceString:(NSString**)number |
| { |
| if (number == NULL) |
| { |
| return NO; |
| } |
| |
| NSString *numberStr = [(*number) copy]; |
| |
| if ([self stringPositionByRegex:numberStr regex:iddPattern] == 0) |
| { |
| NSTextCheckingResult *matched = [[self matchesByRegex:numberStr regex:iddPattern] objectAtIndex:0]; |
| NSString *matchedString = [numberStr substringWithRange:matched.range]; |
| NSUInteger matchEnd = matchedString.length; |
| NSString *remainString = [numberStr substringFromIndex:matchEnd]; |
| |
| NSRegularExpression *currentPattern = self.CAPTURING_DIGIT_PATTERN; |
| NSArray *matchedGroups = [currentPattern matchesInString:remainString options:0 range:NSMakeRange(0, remainString.length)]; |
| |
| if (matchedGroups && [matchedGroups count] > 0 && [matchedGroups objectAtIndex:0] != nil) |
| { |
| NSString *digitMatched = [remainString substringWithRange:((NSTextCheckingResult*)[matchedGroups objectAtIndex:0]).range]; |
| if (digitMatched.length > 0) |
| { |
| NSString *normalizedGroup = [self normalizeDigitsOnly:digitMatched]; |
| if ([normalizedGroup isEqualToString:@"0"]) { |
| return NO; |
| } |
| } |
| } |
| |
| (*number) = [remainString copy]; |
| return YES; |
| } |
| |
| return NO; |
| } |
| |
| |
| /** |
| * Strips any international prefix (such as +, 00, 011) present in the number |
| * provided, normalizes the resulting number, and indicates if an international |
| * prefix was present. |
| * |
| * @param {!goog.string.StringBuffer} number the non-normalized telephone number |
| * that we wish to strip any international dialing prefix from. |
| * @param {string} possibleIddPrefix the international direct dialing prefix |
| * from the region we think this number may be dialed in. |
| * @return {CountryCodeSource} the corresponding |
| * CountryCodeSource if an international dialing prefix could be removed |
| * from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if |
| * the number did not seem to be in international format. |
| */ |
| - (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString**)numberStr possibleIddPrefix:(NSString*)possibleIddPrefix error:(NSError**)error |
| { |
| if (numberStr == NULL) |
| { |
| return 0; |
| } |
| |
| NBECountryCodeSource res = 0; |
| @try { |
| res = [self maybeStripInternationalPrefixAndNormalize:numberStr possibleIddPrefix:possibleIddPrefix]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString**)numberStr possibleIddPrefix:(NSString*)possibleIddPrefix |
| { |
| if (numberStr == NULL || (*numberStr).length == 0) |
| { |
| return NBECountryCodeSourceFROM_DEFAULT_COUNTRY; |
| } |
| |
| // Check to see if the number begins with one or more plus signs. |
| if ([self isStartingStringByRegex:(*numberStr) regex:self.LEADING_PLUS_CHARS_PATTERN_]) |
| { |
| (*numberStr) = [self replaceStringByRegex:(*numberStr) regex:self.LEADING_PLUS_CHARS_PATTERN_ withTemplate:@""]; |
| // Can now normalize the rest of the number since we've consumed the '+' |
| // sign at the start. |
| (*numberStr) = [self normalizePhoneNumber:(*numberStr)]; |
| return NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN; |
| } |
| |
| // Attempt to parse the first digits as an international prefix. |
| NSString *iddPattern = [possibleIddPrefix copy]; |
| [self normalizeSB:numberStr]; |
| |
| return [self parsePrefixAsIdd:iddPattern sourceString:numberStr] ? NBECountryCodeSourceFROM_NUMBER_WITH_IDD : NBECountryCodeSourceFROM_DEFAULT_COUNTRY; |
| } |
| |
| |
| /** |
| * Strips any national prefix (such as 0, 1) present in the number provided. |
| * |
| * @param {!goog.string.StringBuffer} number the normalized telephone number |
| * that we wish to strip any national dialing prefix from. |
| * @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the |
| * region that we think this number is from. |
| * @param {goog.string.StringBuffer} carrierCode a place to insert the carrier |
| * code if one is extracted. |
| * @return {boolean} NO if a national prefix or carrier code (or both) could |
| * be extracted. |
| */ |
| - (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString**)number metadata:(NBPhoneMetaData*)metadata carrierCode:(NSString**)carrierCode error:(NSError**)error |
| { |
| if (number == NULL) |
| { |
| return NO; |
| } |
| |
| BOOL res = NO; |
| @try { |
| res = [self maybeStripNationalPrefixAndCarrierCode:number metadata:metadata carrierCode:carrierCode]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| |
| - (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString**)number metadata:(NBPhoneMetaData*)metadata carrierCode:(NSString**)carrierCode |
| { |
| if (number == NULL) |
| { |
| return NO; |
| } |
| |
| NSString *numberStr = [(*number) copy]; |
| NSUInteger numberLength = numberStr.length; |
| NSString *possibleNationalPrefix = metadata.nationalPrefixForParsing; |
| |
| if (numberLength == 0 || [self hasValue:possibleNationalPrefix] == NO) |
| { |
| // Early return for numbers of zero length. |
| return NO; |
| } |
| |
| // Attempt to parse the first digits as a national prefix. |
| NSString *prefixPattern = [NSString stringWithFormat:@"^(?:%@)", possibleNationalPrefix]; |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:prefixPattern |
| options:0 error:&error]; |
| |
| NSArray *prefixMatcher = [currentPattern matchesInString:numberStr options:0 range:NSMakeRange(0, numberLength)]; |
| if (prefixMatcher && [prefixMatcher count] > 0) |
| { |
| NSString *nationalNumberRule = metadata.generalDesc.nationalNumberPattern; |
| NSTextCheckingResult *firstMatch = [prefixMatcher objectAtIndex:0]; |
| NSString *firstMatchString = [numberStr substringWithRange:firstMatch.range]; |
| |
| // prefixMatcher[numOfGroups] == null implies nothing was captured by the |
| // capturing groups in possibleNationalPrefix; therefore, no transformation |
| // is necessary, and we just remove the national prefix. |
| NSUInteger numOfGroups = firstMatch.numberOfRanges - 1; |
| NSString *transformRule = metadata.nationalPrefixTransformRule; |
| NSString *transformedNumber = @""; |
| NSRange firstRange = [firstMatch rangeAtIndex:numOfGroups]; |
| NSString *firstMatchStringWithGroup = (firstRange.location != NSNotFound && firstRange.location < numberStr.length) ? [numberStr substringWithRange:firstRange] : nil; |
| BOOL noTransform = (transformRule == nil || transformRule.length == 0 || [self hasValue:firstMatchStringWithGroup] == NO); |
| |
| if (noTransform) |
| { |
| transformedNumber = [numberStr substringFromIndex:firstMatchString.length]; |
| } |
| else |
| { |
| transformedNumber = [self replaceFirstStringByRegex:numberStr regex:prefixPattern withTemplate:transformRule]; |
| } |
| // If the original number was viable, and the resultant number is not, |
| // we return. |
| if ([self hasValue:nationalNumberRule ] && [self matchesEntirely:nationalNumberRule string:numberStr] && |
| [self matchesEntirely:nationalNumberRule string:transformedNumber] == NO) |
| { |
| return NO; |
| } |
| |
| if ((noTransform && numOfGroups > 0 && [self hasValue:firstMatchStringWithGroup]) || (!noTransform && numOfGroups > 1)) |
| { |
| if (carrierCode != NULL && (*carrierCode) != nil) |
| { |
| (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchStringWithGroup]; |
| } |
| } |
| else if ((noTransform && numOfGroups > 0 && [self hasValue:firstMatchString]) || (!noTransform && numOfGroups > 1)) |
| { |
| if (carrierCode != NULL && (*carrierCode) != nil) |
| { |
| (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchString]; |
| } |
| } |
| |
| (*number) = transformedNumber; |
| return YES; |
| } |
| return NO; |
| } |
| |
| |
| /** |
| * Strips any extension (as in, the part of the number dialled after the call is |
| * connected, usually indicated with extn, ext, x or similar) from the end of |
| * the number, and returns it. |
| * |
| * @param {!goog.string.StringBuffer} number the non-normalized telephone number |
| * that we wish to strip the extension from. |
| * @return {string} the phone extension. |
| */ |
| - (NSString*)maybeStripExtension:(NSString**)number |
| { |
| if (number == NULL) |
| { |
| return @""; |
| } |
| |
| NSString *numberStr = [(*number) copy]; |
| NSInteger mStart = [self stringPositionByRegex:numberStr regex:self.EXTN_PATTERN_]; |
| |
| // If we find a potential extension, and the number preceding this is a viable |
| // number, we assume it is an extension. |
| if (mStart >= 0 && [self isViablePhoneNumber:[numberStr substringWithRange:NSMakeRange(0, mStart)]]) |
| { |
| // The numbers are captured into groups in the regular expression. |
| NSTextCheckingResult *firstMatch = [self matcheFirstByRegex:numberStr regex:self.EXTN_PATTERN_]; |
| NSUInteger matchedGroupsLength = [firstMatch numberOfRanges]; |
| for (NSUInteger i=1; i<matchedGroupsLength; i++) |
| { |
| NSRange curRange = [firstMatch rangeAtIndex:i]; |
| if (curRange.location != NSNotFound && curRange.location < numberStr.length) |
| { |
| NSString *matchString = [(*number) substringWithRange:curRange]; |
| // We go through the capturing groups until we find one that captured |
| // some digits. If none did, then we will return the empty string. |
| NSString *tokenedString = [numberStr substringWithRange:NSMakeRange(0, mStart)]; |
| (*number) = @""; |
| (*number) = [(*number) stringByAppendingString:tokenedString]; |
| |
| return matchString; |
| } |
| } |
| } |
| |
| return @""; |
| } |
| |
| |
| /** |
| * Checks to see that the region code used is valid, or if it is not valid, that |
| * the number to parse starts with a + symbol so that we can attempt to infer |
| * the region from the number. |
| * @param {string} numberToParse number that we are attempting to parse. |
| * @param {?string} defaultRegion region that we are expecting the number to be |
| * from. |
| * @return {boolean} NO if it cannot use the region provided and the region |
| * cannot be inferred. |
| * @private |
| */ |
| - (BOOL)checkRegionForParsing:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion |
| { |
| // If the number is nil or empty, we can't infer the region. |
| return [self isValidRegionCode:defaultRegion] || |
| (numberToParse != nil && numberToParse.length > 0 && [self isStartingStringByRegex:numberToParse regex:self.LEADING_PLUS_CHARS_PATTERN_]); |
| } |
| |
| |
| /** |
| * Parses a string and returns it in proto buffer format. This method will throw |
| * a {@link i18n.phonenumbers.Error} if the number is not considered to be a |
| * possible number. Note that validation of whether the number is actually a |
| * valid number for a particular region is not performed. This can be done |
| * separately with {@link #isValidNumber}. |
| * |
| * @param {?string} numberToParse number that we are attempting to parse. This |
| * can contain formatting such as +, ( and -, as well as a phone number |
| * extension. It can also be provided in RFC3966 format. |
| * @param {?string} defaultRegion region that we are expecting the number to be |
| * from. This is only used if the number being parsed is not written in |
| * international format. The country_code for the number in this case would |
| * be stored as that of the default region supplied. If the number is |
| * guaranteed to start with a '+' followed by the country calling code, then |
| * 'ZZ' or nil can be supplied. |
| * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled |
| * with the parsed number. |
| * @throws {i18n.phonenumbers.Error} if the string is not considered to be a |
| * viable phone number or if no default region was supplied and the number |
| * is not in international format (does not start with +). |
| */ |
| - (NBPhoneNumber*)parse:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion |
| { |
| return [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:NO checkRegion:YES]; |
| } |
| |
| |
| - (NBPhoneNumber*)parse:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error |
| { |
| NBPhoneNumber *phoneNumber = nil; |
| |
| @try { |
| phoneNumber = [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:NO checkRegion:YES]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| |
| return phoneNumber; |
| } |
| |
| /** |
| * Parses a string using the phone's carrier region (when available, ZZ otherwise). |
| * This uses the country the sim card in the phone is registered with. |
| * For example if you have an AT&T sim card but are in Europe, this will parse the |
| * number using +1 (AT&T is a US Carrier) as the default country code. |
| * This also works for CDMA phones which don't have a sim card. |
| */ |
| - (NBPhoneNumber*)parseWithPhoneCarrierRegion:(NSString*)numberToParse error:(NSError**)error |
| { |
| NSString *(^ISOCountryCodeByCarrier)() = ^() { |
| CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init]; |
| CTCarrier *carrier = [networkInfo subscriberCellularProvider]; |
| return [carrier isoCountryCode]; |
| }; |
| NSString *isoCode = ISOCountryCodeByCarrier(); |
| |
| if (!isoCode) { |
| isoCode = @"ZZ"; |
| } |
| |
| return [self parse:numberToParse defaultRegion:isoCode]; |
| } |
| |
| |
| /** |
| * Parses a string and returns it in proto buffer format. This method differs |
| * from {@link #parse} in that it always populates the raw_input field of the |
| * protocol buffer with numberToParse as well as the country_code_source field. |
| * |
| * @param {string} numberToParse number that we are attempting to parse. This |
| * can contain formatting such as +, ( and -, as well as a phone number |
| * extension. |
| * @param {?string} defaultRegion region that we are expecting the number to be |
| * from. This is only used if the number being parsed is not written in |
| * international format. The country calling code for the number in this |
| * case would be stored as that of the default region supplied. |
| * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled |
| * with the parsed number. |
| * @throws {i18n.phonenumbers.Error} if the string is not considered to be a |
| * viable phone number or if no default region was supplied. |
| */ |
| - (NBPhoneNumber*)parseAndKeepRawInput:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion error:(NSError**)error |
| { |
| NBPhoneNumber *res = nil; |
| @try { |
| res = [self parseAndKeepRawInput:numberToParse defaultRegion:defaultRegion]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NBPhoneNumber*)parseAndKeepRawInput:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion |
| { |
| if ([self isValidRegionCode:defaultRegion] == NO) |
| { |
| if (numberToParse.length > 0 && [numberToParse hasPrefix:@"+"] == NO) |
| { |
| NSException* metaException = [NSException exceptionWithName:@"INVALID_COUNTRY_CODE" |
| reason:[NSString stringWithFormat:@"Invalid country code:%@", numberToParse] |
| userInfo:nil]; |
| @throw metaException; |
| } |
| } |
| return [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:YES checkRegion:YES]; |
| } |
| |
| |
| /** |
| * Parses a string and returns it in proto buffer format. This method is the |
| * same as the public {@link #parse} method, with the exception that it allows |
| * the default region to be nil, for use by {@link #isNumberMatch}. |
| * |
| * @param {?string} numberToParse number that we are attempting to parse. This |
| * can contain formatting such as +, ( and -, as well as a phone number |
| * extension. |
| * @param {?string} defaultRegion region that we are expecting the number to be |
| * from. This is only used if the number being parsed is not written in |
| * international format. The country calling code for the number in this |
| * case would be stored as that of the default region supplied. |
| * @param {boolean} keepRawInput whether to populate the raw_input field of the |
| * phoneNumber with numberToParse. |
| * @param {boolean} checkRegion should be set to NO if it is permitted for |
| * the default coregion to be nil or unknown ('ZZ'). |
| * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled |
| * with the parsed number. |
| * @throws {i18n.phonenumbers.Error} |
| * @private |
| */ |
| - (NBPhoneNumber*)parseHelper:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion |
| keepRawInput:(BOOL)keepRawInput checkRegion:(BOOL)checkRegion error:(NSError**)error |
| { |
| NBPhoneNumber *res = nil; |
| @try { |
| res = [self parseHelper:numberToParse defaultRegion:defaultRegion keepRawInput:keepRawInput checkRegion:checkRegion]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NBPhoneNumber*)parseHelper:(NSString*)numberToParse defaultRegion:(NSString*)defaultRegion |
| keepRawInput:(BOOL)keepRawInput checkRegion:(BOOL)checkRegion |
| { |
| if (numberToParse == nil) |
| { |
| @throw [NSException exceptionWithName:@"NOT_A_NUMBER" reason:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", numberToParse] userInfo:nil]; |
| } |
| else if (numberToParse.length > MAX_INPUT_STRING_LENGTH_) |
| { |
| @throw [NSException exceptionWithName:@"TOO_LONG" reason:[NSString stringWithFormat:@"TOO_LONG:%@", numberToParse] userInfo:nil]; |
| } |
| |
| NSString *nationalNumber = @""; |
| [self buildNationalNumberForParsing:numberToParse nationalNumber:&nationalNumber]; |
| |
| if ([self isViablePhoneNumber:nationalNumber] == NO) |
| { |
| @throw [NSException exceptionWithName:@"NOT_A_NUMBER" reason:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", nationalNumber] userInfo:nil]; |
| } |
| |
| // Check the region supplied is valid, or that the extracted number starts |
| // with some sort of + sign so the number's region can be determined. |
| if (checkRegion && [self checkRegionForParsing:nationalNumber defaultRegion:defaultRegion] == NO) |
| { |
| @throw [NSException exceptionWithName:@"INVALID_COUNTRY_CODE" reason:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", defaultRegion] userInfo:nil]; |
| } |
| |
| NBPhoneNumber *phoneNumber = [[NBPhoneNumber alloc] init]; |
| if (keepRawInput) |
| { |
| phoneNumber.rawInput = [numberToParse copy]; |
| } |
| // Attempt to parse extension first, since it doesn't require region-specific |
| // data and we want to have the non-normalised number here. |
| NSString *extension = [self maybeStripExtension:&nationalNumber]; |
| if (extension.length > 0) |
| { |
| phoneNumber.extension = [extension copy]; |
| } |
| |
| NBPhoneMetaData *regionMetadata = [self getMetadataForRegion:defaultRegion]; |
| // Check to see if the number is given in international format so we know |
| // whether this number is from the default region or not. |
| NSString *normalizedNationalNumber = @""; |
| NSNumber *countryCode = @0; |
| NSString *nationalNumberStr = [nationalNumber copy]; |
| @try { |
| countryCode = [self maybeExtractCountryCode:nationalNumberStr |
| metadata:regionMetadata |
| nationalNumber:&normalizedNationalNumber |
| keepRawInput:keepRawInput |
| phoneNumber:&phoneNumber]; |
| } |
| @catch (NSException *e) { |
| if ([e.name isEqualToString:@"INVALID_COUNTRY_CODE"] && [self stringPositionByRegex:nationalNumberStr |
| regex:self.LEADING_PLUS_CHARS_PATTERN_] >= 0) |
| { |
| // Strip the plus-char, and try again. |
| nationalNumberStr = [self replaceStringByRegex:nationalNumberStr regex:self.LEADING_PLUS_CHARS_PATTERN_ withTemplate:@""]; |
| countryCode = [self maybeExtractCountryCode:nationalNumberStr |
| metadata:regionMetadata |
| nationalNumber:&normalizedNationalNumber |
| keepRawInput:keepRawInput |
| phoneNumber:&phoneNumber]; |
| if ([countryCode isEqualToNumber:@0]) |
| { |
| @throw [NSException exceptionWithName:e.name |
| reason:[NSString stringWithFormat:@"%@:%@", e.name, nationalNumberStr] |
| userInfo:nil]; |
| } |
| } |
| else |
| { |
| @throw e; |
| } |
| } |
| |
| if (![countryCode isEqualToNumber:@0]) |
| { |
| NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCode]; |
| if (phoneNumberRegion != defaultRegion) |
| { |
| // Metadata cannot be nil because the country calling code is valid. |
| regionMetadata = [self getMetadataForRegionOrCallingCode:countryCode regionCode:phoneNumberRegion]; |
| } |
| } |
| else |
| { |
| // If no extracted country calling code, use the region supplied instead. |
| // The national number is just the normalized version of the number we were |
| // given to parse. |
| [self normalizeSB:&nationalNumber]; |
| normalizedNationalNumber = [normalizedNationalNumber stringByAppendingString:nationalNumber]; |
| |
| if (defaultRegion != nil) |
| { |
| countryCode = regionMetadata.countryCode; |
| phoneNumber.countryCode = countryCode; |
| } |
| else if (keepRawInput) |
| { |
| [phoneNumber clearCountryCodeSource]; |
| } |
| } |
| |
| if (normalizedNationalNumber.length < MIN_LENGTH_FOR_NSN_) |
| { |
| @throw [NSException exceptionWithName:@"TOO_SHORT_NSN" |
| reason:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber] |
| userInfo:nil]; |
| } |
| |
| if (regionMetadata != nil) |
| { |
| NSString *carrierCode = @""; |
| [self maybeStripNationalPrefixAndCarrierCode:&normalizedNationalNumber metadata:regionMetadata carrierCode:&carrierCode]; |
| |
| if (keepRawInput) |
| { |
| phoneNumber.PreferredDomesticCarrierCode = [carrierCode copy]; |
| } |
| } |
| |
| NSString *normalizedNationalNumberStr = [normalizedNationalNumber copy]; |
| |
| NSUInteger lengthOfNationalNumber = normalizedNationalNumberStr.length; |
| if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_) |
| { |
| @throw [NSException exceptionWithName:@"TOO_SHORT_NSN" |
| reason:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumberStr] |
| userInfo:nil]; |
| } |
| |
| if (lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_) |
| { |
| @throw [NSException exceptionWithName:@"TOO_LONG" |
| reason:[NSString stringWithFormat:@"TOO_LONG:%@", normalizedNationalNumberStr] |
| userInfo:nil]; |
| } |
| |
| if ([normalizedNationalNumberStr hasPrefix:@"0"]) |
| { |
| phoneNumber.italianLeadingZero = YES; |
| } |
| |
| phoneNumber.nationalNumber = [NSNumber numberWithLongLong:(UInt64)[normalizedNationalNumberStr longLongValue]]; |
| return phoneNumber; |
| } |
| |
| |
| /** |
| * Converts numberToParse to a form that we can parse and write it to |
| * nationalNumber if it is written in RFC3966; otherwise extract a possible |
| * number out of it and write to nationalNumber. |
| * |
| * @param {?string} numberToParse number that we are attempting to parse. This |
| * can contain formatting such as +, ( and -, as well as a phone number |
| * extension. |
| * @param {!goog.string.StringBuffer} nationalNumber a string buffer for storing |
| * the national significant number. |
| * @private |
| */ |
| - (void)buildNationalNumberForParsing:(NSString*)numberToParse nationalNumber:(NSString**)nationalNumber |
| { |
| if (nationalNumber == NULL) |
| return; |
| |
| NSInteger indexOfPhoneContext = [self indexOfStringByString:numberToParse target:RFC3966_PHONE_CONTEXT_]; |
| if (indexOfPhoneContext > 0) |
| { |
| NSUInteger phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT_.length; |
| // If the phone context contains a phone number prefix, we need to capture |
| // it, whereas domains will be ignored. |
| if ([numberToParse characterAtIndex:phoneContextStart] == '+') |
| { |
| // Additional parameters might follow the phone context. If so, we will |
| // remove them here because the parameters after phone context are not |
| // important for parsing the phone number. |
| NSRange foundRange = [numberToParse rangeOfString:@";" options:NSLiteralSearch range:NSMakeRange(phoneContextStart, numberToParse.length - phoneContextStart)]; |
| if (foundRange.location != NSNotFound) |
| { |
| NSRange subRange = NSMakeRange(phoneContextStart, foundRange.location - phoneContextStart); |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[numberToParse substringWithRange:subRange]]; |
| } |
| else |
| { |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[numberToParse substringFromIndex:phoneContextStart]]; |
| } |
| } |
| |
| // Now append everything between the "tel:" prefix and the phone-context. |
| // This should include the national number, an optional extension or |
| // isdn-subaddress component. |
| NSUInteger rfc3966Start = [self indexOfStringByString:numberToParse target:RFC3966_PREFIX_] + RFC3966_PREFIX_.length; |
| NSString *subString = [numberToParse substringWithRange:NSMakeRange(rfc3966Start, indexOfPhoneContext - rfc3966Start)]; |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:subString]; |
| } |
| else |
| { |
| // Extract a possible number from the string passed in (this strips leading |
| // characters that could not be the start of a phone number.) |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[self extractPossibleNumber:numberToParse]]; |
| } |
| |
| // Delete the isdn-subaddress and everything after it if it is present. |
| // Note extension won't appear at the same time with isdn-subaddress |
| // according to paragraph 5.3 of the RFC3966 spec, |
| NSString *nationalNumberStr = [(*nationalNumber) copy]; |
| NSInteger indexOfIsdn = [self indexOfStringByString:nationalNumberStr target:RFC3966_ISDN_SUBADDRESS_]; |
| if (indexOfIsdn > 0) |
| { |
| (*nationalNumber) = @""; |
| (*nationalNumber) = [(*nationalNumber) stringByAppendingString:[nationalNumberStr substringWithRange:NSMakeRange(0, indexOfIsdn)]]; |
| } |
| // If both phone context and isdn-subaddress are absent but other |
| // parameters are present, the parameters are left in nationalNumber. This |
| // is because we are concerned about deleting content from a potential |
| // number string when there is no strong evidence that the number is |
| // actually written in RFC3966. |
| } |
| |
| |
| /** |
| * Takes two phone numbers and compares them for equality. |
| * |
| * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero |
| * for Italian numbers and any extension present are the same. Returns NSN_MATCH |
| * if either or both has no region specified, and the NSNs and extensions are |
| * the same. Returns SHORT_NSN_MATCH if either or both has no region specified, |
| * or the region specified is the same, and one NSN could be a shorter version |
| * of the other number. This includes the case where one has an extension |
| * specified, and the other does not. Returns NO_MATCH otherwise. For example, |
| * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers |
| * +1 345 657 1234 and 345 657 are a NO_MATCH. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber|string} firstNumberIn first number to |
| * compare. If it is a string it can contain formatting, and can have |
| * country calling code specified with + at the start. |
| * @param {i18n.phonenumbers.PhoneNumber|string} secondNumberIn second number to |
| * compare. If it is a string it can contain formatting, and can have |
| * country calling code specified with + at the start. |
| * @return {MatchType} NOT_A_NUMBER, NO_MATCH, |
| * SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of |
| * equality of the two numbers, described in the method definition. |
| */ |
| - (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn error:(NSError**)error |
| { |
| NBEMatchType res = 0; |
| @try { |
| res = [self isNumberMatch:firstNumberIn second:secondNumberIn]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn |
| { |
| |
| // If the input arguements are strings parse them to a proto buffer format. |
| // Else make copies of the phone numbers so that the numbers passed in are not |
| // edited. |
| /** @type {i18n.phonenumbers.PhoneNumber} */ |
| NBPhoneNumber *firstNumber = nil, *secondNumber = nil; |
| if ([firstNumberIn isKindOfClass:[NSString class]]) |
| { |
| // First see if the first number has an implicit country calling code, by |
| // attempting to parse it. |
| @try { |
| firstNumber = [self parse:firstNumberIn defaultRegion:UNKNOWN_REGION_]; |
| } |
| @catch (NSException *e) { |
| if ([e.name isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) |
| { |
| return NBEMatchTypeNOT_A_NUMBER; |
| } |
| // The first number has no country calling code. EXACT_MATCH is no longer |
| // possible. We parse it as if the region was the same as that for the |
| // second number, and if EXACT_MATCH is returned, we replace this with |
| // NSN_MATCH. |
| if ([secondNumberIn isKindOfClass:[NSString class]] == NO) |
| { |
| NSString *secondNumberRegion = [self getRegionCodeForCountryCode:((NBPhoneNumber*)secondNumberIn).countryCode]; |
| if (secondNumberRegion != UNKNOWN_REGION_) |
| { |
| @try { |
| firstNumber = [self parse:firstNumberIn defaultRegion:secondNumberRegion]; |
| } |
| @catch (NSException *e2) { |
| return NBEMatchTypeNOT_A_NUMBER; |
| } |
| |
| NBEMatchType match = [self isNumberMatch:firstNumber second:secondNumberIn]; |
| if (match == NBEMatchTypeEXACT_MATCH) |
| { |
| return NBEMatchTypeNSN_MATCH; |
| } |
| return match; |
| } |
| } |
| // If the second number is a string or doesn't have a valid country |
| // calling code, we parse the first number without country calling code. |
| @try { |
| firstNumber = [self parseHelper:firstNumberIn defaultRegion:nil keepRawInput:NO checkRegion:NO]; |
| } |
| @catch (NSException *e2) { |
| return NBEMatchTypeNOT_A_NUMBER; |
| } |
| } |
| } |
| else |
| { |
| firstNumber = [firstNumberIn copy]; |
| } |
| |
| if ([secondNumberIn isKindOfClass:[NSString class]]) |
| { |
| @try { |
| secondNumber = [self parse:secondNumberIn defaultRegion:UNKNOWN_REGION_]; |
| return [self isNumberMatch:firstNumberIn second:secondNumber]; |
| } |
| @catch (NSException *e2) { |
| if ([e2.name isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) |
| { |
| return NBEMatchTypeNOT_A_NUMBER; |
| } |
| return [self isNumberMatch:secondNumberIn second:firstNumber]; |
| } |
| } |
| else |
| { |
| secondNumber = [secondNumberIn copy]; |
| } |
| |
| // First clear raw_input, country_code_source and |
| // preferred_domestic_carrier_code fields and any empty-string extensions so |
| // that we can use the proto-buffer equality method. |
| firstNumber.rawInput = @""; |
| [firstNumber clearCountryCodeSource]; |
| firstNumber.PreferredDomesticCarrierCode = @""; |
| |
| secondNumber.rawInput = @""; |
| [secondNumber clearCountryCodeSource]; |
| secondNumber.PreferredDomesticCarrierCode = @""; |
| |
| if (firstNumber.extension != nil && firstNumber.extension.length == 0) |
| { |
| firstNumber.extension = nil; |
| } |
| |
| if (secondNumber.extension != nil && secondNumber.extension.length == 0) |
| { |
| secondNumber.extension = nil; |
| } |
| |
| // Early exit if both had extensions and these are different. |
| if ([self hasValue:firstNumber.extension] && [self hasValue:secondNumber.extension] && |
| [firstNumber.extension isEqualToString:secondNumber.extension] == NO) |
| { |
| return NBEMatchTypeNO_MATCH; |
| } |
| |
| NSNumber *firstNumberCountryCode = firstNumber.countryCode; |
| NSNumber *secondNumberCountryCode = secondNumber.countryCode; |
| |
| // Both had country_code specified. |
| if (![firstNumberCountryCode isEqualToNumber:@0] && ![secondNumberCountryCode isEqualToNumber:@0]) |
| { |
| if ([firstNumber isEqual:secondNumber]) |
| { |
| return NBEMatchTypeEXACT_MATCH; |
| } |
| else if ([firstNumberCountryCode isEqualToNumber:secondNumberCountryCode] && [self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) |
| { |
| // A SHORT_NSN_MATCH occurs if there is a difference because of the |
| // presence or absence of an 'Italian leading zero', the presence or |
| // absence of an extension, or one NSN being a shorter variant of the |
| // other. |
| return NBEMatchTypeSHORT_NSN_MATCH; |
| } |
| // This is not a match. |
| return NBEMatchTypeNO_MATCH; |
| } |
| // Checks cases where one or both country_code fields were not specified. To |
| // make equality checks easier, we first set the country_code fields to be |
| // equal. |
| firstNumber.countryCode = @0; |
| secondNumber.countryCode = @0; |
| // If all else was the same, then this is an NSN_MATCH. |
| if ([firstNumber isEqual:secondNumber]) |
| { |
| return NBEMatchTypeNSN_MATCH; |
| } |
| |
| if ([self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) |
| { |
| return NBEMatchTypeSHORT_NSN_MATCH; |
| } |
| return NBEMatchTypeNO_MATCH; |
| } |
| |
| |
| /** |
| * Returns NO when one national number is the suffix of the other or both are |
| * the same. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} firstNumber the first PhoneNumber |
| * object. |
| * @param {i18n.phonenumbers.PhoneNumber} secondNumber the second PhoneNumber |
| * object. |
| * @return {boolean} NO if one PhoneNumber is the suffix of the other one. |
| * @private |
| */ |
| - (BOOL)isNationalNumberSuffixOfTheOther:(NBPhoneNumber*)firstNumber second:(NBPhoneNumber*)secondNumber |
| { |
| NSString *firstNumberNationalNumber = [NSString stringWithFormat:@"%@", firstNumber.nationalNumber]; |
| NSString *secondNumberNationalNumber = [NSString stringWithFormat:@"%@", secondNumber.nationalNumber]; |
| |
| // Note that endsWith returns NO if the numbers are equal. |
| return [firstNumberNationalNumber hasSuffix:secondNumberNationalNumber] || |
| [secondNumberNationalNumber hasSuffix:firstNumberNationalNumber]; |
| } |
| |
| |
| /** |
| * Returns NO if the number can be dialled from outside the region, or |
| * unknown. If the number can only be dialled from within the region, returns |
| * NO. Does not check the number is a valid number. |
| * TODO: Make this method public when we have enough metadata to make it |
| * worthwhile. Currently visible for testing purposes only. |
| * |
| * @param {i18n.phonenumbers.PhoneNumber} number the phone-number for which we |
| * want to know whether it is diallable from outside the region. |
| * @return {boolean} NO if the number can only be dialled from within the |
| * country. |
| */ |
| - (BOOL)canBeInternationallyDialled:(NBPhoneNumber*)number error:(NSError**)error |
| { |
| BOOL res = NO; |
| @try { |
| res = [self canBeInternationallyDialled:number]; |
| } |
| @catch (NSException *exception) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:exception.reason |
| forKey:NSLocalizedDescriptionKey]; |
| if (error != NULL) |
| (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo]; |
| } |
| return res; |
| } |
| |
| - (BOOL)canBeInternationallyDialled:(NBPhoneNumber*)number |
| { |
| NBPhoneMetaData *metadata = [self getMetadataForRegion:[self getRegionCodeForNumber:number]]; |
| if (metadata == nil) |
| { |
| // Note numbers belonging to non-geographical entities (e.g. +800 numbers) |
| // are always internationally diallable, and will be caught here. |
| return YES; |
| } |
| NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number]; |
| return [self isNumberMatchingDesc:nationalSignificantNumber numberDesc:metadata.noInternationalDialling] == NO; |
| } |
| |
| |
| /** |
| * Check whether the entire input sequence can be matched against the regular |
| * expression. |
| * |
| * @param {!RegExp|string} regex the regular expression to match against. |
| * @param {string} str the string to test. |
| * @return {boolean} NO if str can be matched entirely against regex. |
| * @private |
| */ |
| - (BOOL)matchesEntirely:(NSString*)regex string:(NSString*)str |
| { |
| if ([regex rangeOfString:@"^"].location == NSNotFound) |
| { |
| regex = [NSString stringWithFormat:@"^(?:%@)$", regex]; |
| } |
| |
| NSError *error = nil; |
| NSRegularExpression *currentPattern = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:&error]; |
| NSTextCheckingResult *matchResult = [currentPattern firstMatchInString:str options:0 range:NSMakeRange(0, str.length)]; |
| |
| if (matchResult != nil) |
| { |
| NSString *founds = [str substringWithRange:matchResult.range]; |
| |
| if ([founds isEqualToString:str]) |
| { |
| return YES; |
| } |
| } |
| |
| return NO; |
| } |
| |
| |
| @end |