blob: bfb95c9c17dc30ac5113b336905436f1c7af2b7c [file] [log] [blame]
//
// 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