See: https://github.com/googlei18n/libphonenumber/blob/master/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java#L908
4095 lines
175 KiB
Objective-C
4095 lines
175 KiB
Objective-C
//
|
||
// NBPhoneNumberUtil.m
|
||
// libPhoneNumber
|
||
//
|
||
// Created by tabby on 2015. 2. 8..
|
||
// Copyright (c) 2015년 ohtalk.me. All rights reserved.
|
||
//
|
||
|
||
#import "NBPhoneNumberUtil.h"
|
||
#import <math.h>
|
||
#import "NBMetadataHelper.h"
|
||
#import "NBNumberFormat.h"
|
||
#import "NBPhoneMetaData.h"
|
||
#import "NBPhoneNumber.h"
|
||
#import "NBPhoneNumberDefines.h"
|
||
#import "NBPhoneNumberDesc.h"
|
||
#import "NBRegExMatcher.h"
|
||
|
||
#if TARGET_OS_IOS
|
||
#import <CoreTelephony/CTCarrier.h>
|
||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||
#endif
|
||
|
||
static NSString *NormalizeNonBreakingSpace(NSString *aString) {
|
||
return [aString stringByReplacingOccurrencesOfString:NB_NON_BREAKING_SPACE withString:@" "];
|
||
}
|
||
|
||
static BOOL isNan(NSString *sourceString) {
|
||
static dispatch_once_t onceToken;
|
||
static NSCharacterSet *nonDecimalCharacterSet;
|
||
dispatch_once(&onceToken, ^{
|
||
nonDecimalCharacterSet = [[NSMutableCharacterSet decimalDigitCharacterSet] invertedSet];
|
||
});
|
||
|
||
// Return YES if the sourceString doesn't have any characters that can be represented as a Float.
|
||
return !([sourceString rangeOfCharacterFromSet:nonDecimalCharacterSet].location == NSNotFound);
|
||
}
|
||
|
||
#pragma mark - NBPhoneNumberUtil interface -
|
||
|
||
@interface NBPhoneNumberUtil ()
|
||
|
||
@property(nonatomic, strong) NSLock *entireStringCacheLock;
|
||
@property(nonatomic, strong) NSMutableDictionary *entireStringRegexCache;
|
||
|
||
@property(nonatomic, strong) NSLock *lockPatternCache;
|
||
@property(nonatomic, strong) NSMutableDictionary *regexPatternCache;
|
||
|
||
@property(nonatomic, strong) NSRegularExpression *CAPTURING_DIGIT_PATTERN;
|
||
@property(nonatomic, strong) NSRegularExpression *VALID_ALPHA_PHONE_PATTERN;
|
||
|
||
@property(nonatomic, strong, readwrite) NBMetadataHelper *helper;
|
||
@property(nonatomic, strong, readwrite) NBRegExMatcher *matcher;
|
||
|
||
#if TARGET_OS_IOS
|
||
@property(nonatomic, readonly) CTTelephonyNetworkInfo *telephonyNetworkInfo;
|
||
#endif
|
||
|
||
@end
|
||
|
||
@implementation NBPhoneNumberUtil
|
||
|
||
#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 -
|
||
static NSString *VALID_PUNCTUATION =
|
||
@"-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~";
|
||
|
||
static NSString *COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = @"3";
|
||
static NSString *PLUS_SIGN = @"+";
|
||
static NSString *STAR_SIGN = @"*";
|
||
static NSString *RFC3966_EXTN_PREFIX = @";ext=";
|
||
static NSString *RFC3966_PREFIX = @"tel:";
|
||
static NSString *RFC3966_PHONE_CONTEXT = @";phone-context=";
|
||
static NSString *RFC3966_ISDN_SUBADDRESS = @";isub=";
|
||
static NSString *DEFAULT_EXTN_PREFIX = @" ext. ";
|
||
static NSString *VALID_ALPHA = @"A-Za-z";
|
||
|
||
#pragma mark - Static regular expression strings -
|
||
static NSString *NON_DIGITS_PATTERN = @"\\D+";
|
||
static NSString *CC_PATTERN = @"\\$CC";
|
||
static NSString *FIRST_GROUP_PATTERN = @"(\\$\\d)";
|
||
static NSString *FIRST_GROUP_ONLY_PREFIX_PATTERN = @"^\\(?\\$1\\)?";
|
||
static NSString *NP_PATTERN = @"\\$NP";
|
||
static NSString *FG_PATTERN = @"\\$FG";
|
||
static NSString *VALID_ALPHA_PHONE_PATTERN_STRING = @"(?:.*?[A-Za-z]){3}.*";
|
||
|
||
static NSString *UNIQUE_INTERNATIONAL_PREFIX = @"[\\d]+(?:[~\\u2053\\u223C\\uFF5E][\\d]+)?";
|
||
|
||
static NSString *LEADING_PLUS_CHARS_PATTERN;
|
||
static NSString *EXTN_PATTERN;
|
||
static NSString *SEPARATOR_PATTERN;
|
||
static NSString *VALID_PHONE_NUMBER_PATTERN;
|
||
static NSString *VALID_START_CHAR_PATTERN;
|
||
static NSString *UNWANTED_END_CHAR_PATTERN;
|
||
static NSString *SECOND_NUMBER_START_PATTERN;
|
||
|
||
static NSDictionary *ALL_NORMALIZATION_MAPPINGS;
|
||
static NSDictionary *DIALLABLE_CHAR_MAPPINGS;
|
||
static NSDictionary *ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
|
||
|
||
// Map of country calling codes that use a mobile token before the area code. One example of when
|
||
// this is relevant is when determining the length of the national destination code, which should
|
||
// be the length of the area code plus the length of the mobile token.
|
||
static NSDictionary<NSNumber *, NSString *> *MOBILE_TOKEN_MAPPINGS;
|
||
|
||
static NSDictionary *DIGIT_MAPPINGS;
|
||
|
||
static NSArray *GEO_MOBILE_COUNTRIES;
|
||
|
||
#pragma mark - Deprecated methods
|
||
|
||
+ (NBPhoneNumberUtil *)sharedInstance {
|
||
static NBPhoneNumberUtil *sharedOnceInstance = nil;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
sharedOnceInstance = [[self alloc] init];
|
||
});
|
||
return sharedOnceInstance;
|
||
}
|
||
|
||
#pragma mark - NSError
|
||
|
||
- (NSError *)errorWithObject:(id)obj withDomain:(NSString *)domain {
|
||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:obj forKey:NSLocalizedDescriptionKey];
|
||
NSError *error = [NSError errorWithDomain:domain code:0 userInfo:userInfo];
|
||
return error;
|
||
}
|
||
|
||
- (NSRegularExpression *)entireRegularExpressionWithPattern:(NSString *)regexPattern
|
||
options:(NSRegularExpressionOptions)options
|
||
error:(NSError **)error {
|
||
[_entireStringCacheLock lock];
|
||
|
||
@try {
|
||
if (!_entireStringRegexCache) {
|
||
_entireStringRegexCache = [[NSMutableDictionary alloc] init];
|
||
}
|
||
|
||
NSRegularExpression *regex = [_entireStringRegexCache objectForKey:regexPattern];
|
||
if (!regex) {
|
||
NSString *finalRegexString = regexPattern;
|
||
if ([regexPattern rangeOfString:@"^"].location == NSNotFound) {
|
||
finalRegexString = [NSString stringWithFormat:@"^(?:%@)$", regexPattern];
|
||
}
|
||
|
||
regex = [self regularExpressionWithPattern:finalRegexString options:0 error:error];
|
||
[_entireStringRegexCache setObject:regex forKey:regexPattern];
|
||
}
|
||
|
||
return regex;
|
||
} @finally {
|
||
[_entireStringCacheLock unlock];
|
||
}
|
||
}
|
||
|
||
- (NSRegularExpression *)regularExpressionWithPattern:(NSString *)pattern
|
||
options:(NSRegularExpressionOptions)options
|
||
error:(NSError **)error {
|
||
[_lockPatternCache lock];
|
||
|
||
@try {
|
||
if (!_regexPatternCache) {
|
||
_regexPatternCache = [[NSMutableDictionary alloc] init];
|
||
}
|
||
|
||
NSRegularExpression *regex = [_regexPatternCache objectForKey:pattern];
|
||
if (!regex) {
|
||
regex =
|
||
[NSRegularExpression regularExpressionWithPattern:pattern options:options error:error];
|
||
[_regexPatternCache setObject:regex forKey:pattern];
|
||
}
|
||
return regex;
|
||
} @finally {
|
||
[_lockPatternCache unlock];
|
||
}
|
||
}
|
||
|
||
- (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;
|
||
}
|
||
|
||
- (int)stringPositionByRegex:(NSString *)sourceString regex:(NSString *)pattern {
|
||
if (sourceString == nil || sourceString.length <= 0 || pattern == nil || pattern.length <= 0) {
|
||
return -1;
|
||
}
|
||
|
||
NSError *error = nil;
|
||
NSRegularExpression *currentPattern =
|
||
[self regularExpressionWithPattern:pattern options:0 error:&error];
|
||
NSArray *matches = [currentPattern matchesInString:sourceString
|
||
options:0
|
||
range:NSMakeRange(0, sourceString.length)];
|
||
|
||
int foundPosition = -1;
|
||
|
||
if (matches.count > 0) {
|
||
NSTextCheckingResult *match = [matches objectAtIndex:0];
|
||
return (int)match.range.location;
|
||
}
|
||
|
||
return foundPosition;
|
||
}
|
||
|
||
- (int)indexOfStringByString:(NSString *)sourceString target:(NSString *)targetString {
|
||
NSRange finded = [sourceString rangeOfString:targetString];
|
||
if (finded.location != NSNotFound) {
|
||
return (int)finded.location;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
- (NSString *)replaceFirstStringByRegex:(NSString *)sourceString
|
||
regex:(NSString *)pattern
|
||
withTemplate:(NSString *)templateString {
|
||
NSString *replacementResult = [sourceString copy];
|
||
NSError *error = nil;
|
||
|
||
NSRegularExpression *currentPattern =
|
||
[self 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 {
|
||
NSError *error = nil;
|
||
NSRegularExpression *currentPattern =
|
||
[self regularExpressionWithPattern:pattern options:0 error:&error];
|
||
NSArray *matches = [currentPattern matchesInString:sourceString
|
||
options:0
|
||
range:NSMakeRange(0, sourceString.length)];
|
||
|
||
if ([matches count] == 1) {
|
||
NSString *replacementResult;
|
||
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];
|
||
} else {
|
||
replacementResult = [sourceString copy];
|
||
}
|
||
return replacementResult;
|
||
}
|
||
|
||
if ([matches count] > 1) {
|
||
return [currentPattern stringByReplacingMatchesInString:sourceString
|
||
options:0
|
||
range:NSMakeRange(0, sourceString.length)
|
||
withTemplate:templateString];
|
||
}
|
||
|
||
return [sourceString copy];
|
||
}
|
||
|
||
- (NSTextCheckingResult *)matchFirstByRegex:(NSString *)sourceString regex:(NSString *)pattern {
|
||
NSError *error = nil;
|
||
NSRegularExpression *currentPattern =
|
||
[self 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 =
|
||
[self 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 =
|
||
[self 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] init];
|
||
NSUInteger length = sourceString.length;
|
||
for (NSUInteger i = 0; i < 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)isAllDigits:(NSString *)sourceString {
|
||
NSCharacterSet *nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
|
||
NSRange r = [sourceString rangeOfCharacterFromSet:nonNumbers];
|
||
return r.location == NSNotFound;
|
||
}
|
||
|
||
/**
|
||
* Gets the national significant number of the a phone number. Note a national
|
||
* significant number doesn't contain a national prefix or any formatting.
|
||
*
|
||
* - param {i18n.phonenumbers.PhoneNumber} number the phone number for which the
|
||
* national significant number is needed.
|
||
* @return {string} the national significant number of the PhoneNumber object
|
||
* passed in.
|
||
*/
|
||
- (NSString *)getNationalSignificantNumber:(NBPhoneNumber *)phoneNumber {
|
||
// If leading zero(s) have been set, we prefix this now. Note this is not a
|
||
// national prefix.
|
||
NSString *nationalNumber = [phoneNumber.nationalNumber stringValue];
|
||
if (phoneNumber.italianLeadingZero) {
|
||
NSString *zeroNumbers =
|
||
[@"" stringByPaddingToLength:phoneNumber.numberOfLeadingZeros.integerValue
|
||
withString:@"0"
|
||
startingAtIndex:0];
|
||
return [NSString stringWithFormat:@"%@%@", zeroNumbers, nationalNumber];
|
||
}
|
||
|
||
return [phoneNumber.nationalNumber stringValue];
|
||
}
|
||
|
||
#pragma mark - Initializations -
|
||
|
||
+ (void)initialize {
|
||
[super initialize];
|
||
|
||
/**
|
||
* Set of country calling codes that have geographically assigned mobile
|
||
* numbers. This may not be complete; we add calling codes case by case, as we
|
||
* find geographical mobile numbers or hear from user reports.
|
||
*
|
||
* @const
|
||
* @type {!Array.<number>}
|
||
* @private
|
||
*/
|
||
// @[ Mexico, Argentina, Brazil ]
|
||
GEO_MOBILE_COUNTRIES = @[ @52, @54, @55 ];
|
||
}
|
||
|
||
- (instancetype)init {
|
||
self = [super init];
|
||
if (self) {
|
||
_lockPatternCache = [[NSLock alloc] init];
|
||
_entireStringCacheLock = [[NSLock alloc] init];
|
||
_helper = [[NBMetadataHelper alloc] init];
|
||
_matcher = [[NBRegExMatcher alloc] init];
|
||
[self initRegularExpressionSet];
|
||
[self initNormalizationMappings];
|
||
}
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)initRegularExpressionSet {
|
||
NSError *error = nil;
|
||
|
||
if (!_CAPTURING_DIGIT_PATTERN) {
|
||
_CAPTURING_DIGIT_PATTERN = [self
|
||
regularExpressionWithPattern:[NSString stringWithFormat:@"([%@])", NB_VALID_DIGITS_STRING]
|
||
options:0
|
||
error:&error];
|
||
}
|
||
|
||
if (!_VALID_ALPHA_PHONE_PATTERN) {
|
||
_VALID_ALPHA_PHONE_PATTERN =
|
||
[self regularExpressionWithPattern:VALID_ALPHA_PHONE_PATTERN_STRING options:0 error:&error];
|
||
}
|
||
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(
|
||
&onceToken, ^{
|
||
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})#)$";
|
||
|
||
LEADING_PLUS_CHARS_PATTERN = [NSString stringWithFormat:@"^[%@]+", NB_PLUS_CHARS];
|
||
|
||
VALID_START_CHAR_PATTERN =
|
||
[NSString stringWithFormat:@"[%@%@]", NB_PLUS_CHARS, NB_VALID_DIGITS_STRING];
|
||
|
||
SECOND_NUMBER_START_PATTERN = @"[\\\\\\/] *x";
|
||
|
||
UNWANTED_END_CHAR_PATTERN =
|
||
[NSString stringWithFormat:@"[^%@%@#]+$", NB_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})#)?$";
|
||
});
|
||
}
|
||
|
||
- (NSDictionary *)DIGIT_MAPPINGS {
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
DIGIT_MAPPINGS = [NSDictionary
|
||
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",
|
||
// BENGALI digit 0 to 9
|
||
@"0", @"\u09E6", @"1", @"\u09E7", @"2", @"\u09E8", @"3",
|
||
@"\u09E9", @"4", @"\u09EA", @"5", @"\u09EB", @"6", @"\u09EC",
|
||
@"7", @"\u09ED", @"8", @"\u09EE", @"9", @"\u09EF",
|
||
// DEVANAGARI digit 0 to 9
|
||
@"0", @"\u0966", @"1", @"\u0967", @"2", @"\u0968", @"3",
|
||
@"\u0969", @"4", @"\u096A", @"5", @"\u096B", @"6", @"\u096C",
|
||
@"7", @"\u096D", @"8", @"\u096E", @"9", @"\u096F", nil];
|
||
});
|
||
|
||
return DIGIT_MAPPINGS;
|
||
}
|
||
|
||
- (void)initNormalizationMappings {
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
DIALLABLE_CHAR_MAPPINGS = [NSDictionary
|
||
dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
|
||
@"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
|
||
@"+", @"+", @"*", @"*", @"#", @"#", nil];
|
||
|
||
ALL_NORMALIZATION_MAPPINGS = [NSDictionary
|
||
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 = [NSDictionary
|
||
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];
|
||
|
||
MOBILE_TOKEN_MAPPINGS = @{
|
||
@52: @"1",
|
||
@54: @"9",
|
||
};
|
||
});
|
||
}
|
||
|
||
#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 {
|
||
number = NormalizeNonBreakingSpace(number);
|
||
|
||
NSString *possibleNumber = @"";
|
||
int start = [self stringPositionByRegex:number regex:VALID_START_CHAR_PATTERN];
|
||
|
||
if (start >= 0) {
|
||
possibleNumber = [number substringFromIndex:start];
|
||
// Remove trailing non-alpha non-numerical characters.
|
||
possibleNumber =
|
||
[self replaceStringByRegex:possibleNumber regex:UNWANTED_END_CHAR_PATTERN withTemplate:@""];
|
||
|
||
// Check for extra numbers at the end.
|
||
int secondNumberStart =
|
||
[self stringPositionByRegex:possibleNumber regex:SECOND_NUMBER_START_PATTERN];
|
||
if (secondNumberStart > 0) {
|
||
possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, secondNumberStart)];
|
||
}
|
||
} else {
|
||
possibleNumber = @"";
|
||
}
|
||
|
||
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 {
|
||
phoneNumber = NormalizeNonBreakingSpace(phoneNumber);
|
||
|
||
if (phoneNumber.length < MIN_LENGTH_FOR_NSN_) {
|
||
return NO;
|
||
}
|
||
|
||
return [self matchesEntirely: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 *)normalize:(NSString *)number {
|
||
if ([self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:number]) {
|
||
return [self normalizeHelper:number
|
||
normalizationReplacements: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 normalize:(*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 {
|
||
number = NormalizeNonBreakingSpace(number);
|
||
|
||
return [self stringByReplacingOccurrencesString:number
|
||
withMap:self.DIGIT_MAPPINGS
|
||
removeNonMatches:YES];
|
||
}
|
||
|
||
/**
|
||
* Normalizes a string of characters representing a phone number. This strips
|
||
* all characters which are not diallable on a mobile phone keypad (including
|
||
* all non-ASCII digits).
|
||
*
|
||
* - param {string} number a string of characters representing a phone number.
|
||
* @return {string} the normalized string version of the phone number.
|
||
*/
|
||
- (NSString *)normalizeDiallableCharsOnly:(NSString *)number {
|
||
number = NormalizeNonBreakingSpace(number);
|
||
|
||
return [self stringByReplacingOccurrencesString:number
|
||
withMap:DIALLABLE_CHAR_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 {
|
||
number = NormalizeNonBreakingSpace(number);
|
||
return [self stringByReplacingOccurrencesString:number
|
||
withMap: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.
|
||
*/
|
||
- (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
|
||
int 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;
|
||
}
|
||
|
||
- (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber {
|
||
NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
|
||
|
||
NBPhoneMetaData *metadata = [self.helper 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.
|
||
*/
|
||
- (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
|
||
int 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;
|
||
}
|
||
|
||
- (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber {
|
||
NBPhoneNumber *copiedProto = nil;
|
||
|
||
if ([NBMetadataHelper 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 = [NBMetadataHelper 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 (int)((NSString *)[numberGroups objectAtIndex:2]).length + 1;
|
||
}
|
||
|
||
return (int)((NSString *)[numberGroups objectAtIndex:1]).length;
|
||
}
|
||
|
||
- (NSString *)getCountryMobileTokenFromCountryCode:(NSInteger)countryCallingCode {
|
||
NSString *mobileToken = MOBILE_TOKEN_MAPPINGS[@(countryCallingCode)];
|
||
if (mobileToken != nil) {
|
||
return mobileToken;
|
||
}
|
||
return @"";
|
||
}
|
||
|
||
/**
|
||
* 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 {
|
||
NSUInteger numberLength = sourceString.length;
|
||
NSMutableString *normalizedNumber = [[NSMutableString alloc] init];
|
||
|
||
for (NSUInteger i = 0; i < numberLength; ++i) {
|
||
NSString *charString = [sourceString substringWithRange:NSMakeRange(i, 1)];
|
||
NSString *newDigit = [normalizationReplacements objectForKey:[charString uppercaseString]];
|
||
if (newDigit != nil) {
|
||
[normalizedNumber appendString:newDigit];
|
||
} else if (removeNonMatches == NO) {
|
||
[normalizedNumber appendString:charString];
|
||
}
|
||
// 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 = [self stringPositionByRegex:nationalPrefixFormattingRule
|
||
regex:FIRST_GROUP_ONLY_PREFIX_PATTERN] >= 0;
|
||
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.
|
||
|
||
BOOL containGeoMobileContries = [GEO_MOBILE_COUNTRIES containsObject:phoneNumber.countryCode] &&
|
||
numberType == NBEPhoneNumberTypeMOBILE;
|
||
BOOL isFixedLine = (numberType == NBEPhoneNumberTypeFIXED_LINE);
|
||
BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
|
||
return isFixedLine || isFixedLineOrMobile || containGeoMobileContries;
|
||
}
|
||
|
||
/**
|
||
* 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 [NBMetadataHelper hasValue:regionCode] && isNan(regionCode) &&
|
||
[self.helper 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 = [NBMetadataHelper 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] &&
|
||
[NBMetadataHelper 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 ([NBMetadataHelper 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 = [NBMetadataHelper 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 = [NBMetadataHelper 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 {
|
||
NBMetadataHelper *helper = self.helper;
|
||
|
||
return [regionCode isEqualToString:NB_REGION_CODE_FOR_NON_GEO_ENTITY]
|
||
? [helper getMetadataForNonGeographicalRegion:countryCallingCode]
|
||
: [helper 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 [NBMetadataHelper 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 = [NBMetadataHelper 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.unsignedIntegerValue == NANPA_COUNTRY_CODE_ ||
|
||
[regionCode isEqualToString:NB_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: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 copy];
|
||
NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
|
||
|
||
if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
|
||
return nationalSignificantNumber;
|
||
}
|
||
|
||
if (countryCallingCode.unsignedIntegerValue == 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 error:nil]]) {
|
||
// 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.helper 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 ([NBMetadataHelper
|
||
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 ([NBMetadataHelper hasValue:number.rawInput] &&
|
||
([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 integerValue]) {
|
||
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.helper 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 {
|
||
BOOL isValid = NO;
|
||
NSString *normalizedNationalNumber = [self normalizeDigitsOnly:rawInput];
|
||
if ([self isStartingStringByRegex:normalizedNationalNumber regex:nationalPrefix]) {
|
||
// 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];
|
||
NSError *anError = nil;
|
||
isValid = [self isValidNumber:[self parse:subString defaultRegion:regionCode error:&anError]];
|
||
|
||
if (anError != nil) return NO;
|
||
}
|
||
return isValid;
|
||
}
|
||
|
||
/**
|
||
* 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) {
|
||
int firstNationalNumberDigit =
|
||
[self indexOfStringByString:rawInput
|
||
target:[nationalNumber substringWithRange:NSMakeRange(0, 3)]];
|
||
if (firstNationalNumberDigit != -1) {
|
||
rawInput = [rawInput substringFromIndex:firstNationalNumberDigit];
|
||
}
|
||
}
|
||
|
||
NBPhoneMetaData *metadataForRegionCallingFrom =
|
||
[self.helper getMetadataForRegion:regionCallingFrom];
|
||
|
||
if (countryCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_) {
|
||
if ([self isNANPACountry:regionCallingFrom]) {
|
||
return [NSString stringWithFormat:@"%@ %@", countryCode, rawInput];
|
||
}
|
||
} else if (metadataForRegionCallingFrom != nil &&
|
||
[countryCode
|
||
isEqualToNumber:[self getCountryCodeForValidRegion:regionCallingFrom error:nil]]) {
|
||
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 {
|
||
NSArray *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 && [NBMetadataHelper 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 &&
|
||
[NBMetadataHelper 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:@"^%@", SEPARATOR_PATTERN]
|
||
withTemplate:@""];
|
||
|
||
// Replace the rest with a dash between each number group.
|
||
formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber
|
||
regex: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 =
|
||
[self getExampleNumberForType:regionCode type:NBEPhoneNumberTypeFIXED_LINE error:error];
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
|
||
if ([self isValidRegionCode:regionCode] == NO) {
|
||
return nil;
|
||
}
|
||
|
||
NBPhoneNumberDesc *desc =
|
||
[self getNumberDescByType:[self.helper getMetadataForRegion:regionCode] type:type];
|
||
|
||
if ([NBMetadataHelper hasValue:desc.exampleNumber]) {
|
||
return [self parse:desc.exampleNumber defaultRegion:regionCode error:error];
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
|
||
NBPhoneMetaData *metadata = [self.helper getMetadataForNonGeographicalRegion:countryCallingCode];
|
||
|
||
if (metadata != nil) {
|
||
NSString *fetchedExampleNumber = nil;
|
||
if ([NBMetadataHelper hasValue:metadata.mobile.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.mobile.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.tollFree.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.tollFree.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.sharedCost.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.sharedCost.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.voip.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.voip.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.voicemail.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.voicemail.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.uan.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.uan.exampleNumber;
|
||
} else if ([NBMetadataHelper hasValue:metadata.premiumRate.exampleNumber]) {
|
||
fetchedExampleNumber = metadata.premiumRate.exampleNumber;
|
||
}
|
||
|
||
if (fetchedExampleNumber != nil) {
|
||
NSString *callCode =
|
||
[NSString stringWithFormat:@"+%@%@", countryCallingCode, fetchedExampleNumber];
|
||
res = [self parse:callCode defaultRegion:NB_UNKNOWN_REGION error:error];
|
||
}
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 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 ([NBMetadataHelper hasValue:number.extension] == NO) {
|
||
return @"";
|
||
} else {
|
||
if (numberFormat == NBEPhoneNumberFormatRFC3966) {
|
||
return [NSString stringWithFormat:@"%@%@", RFC3966_EXTN_PREFIX, number.extension];
|
||
} else {
|
||
if ([NBMetadataHelper 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;
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:generalNumberDesc] == NO) {
|
||
return NBEPhoneNumberTypeUNKNOWN;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.premiumRate]) {
|
||
return NBEPhoneNumberTypePREMIUM_RATE;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.tollFree]) {
|
||
return NBEPhoneNumberTypeTOLL_FREE;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.sharedCost]) {
|
||
return NBEPhoneNumberTypeSHARED_COST;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voip]) {
|
||
return NBEPhoneNumberTypeVOIP;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.personalNumber]) {
|
||
return NBEPhoneNumberTypePERSONAL_NUMBER;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.pager]) {
|
||
return NBEPhoneNumberTypePAGER;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.uan]) {
|
||
return NBEPhoneNumberTypeUAN;
|
||
}
|
||
|
||
if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voicemail]) {
|
||
return NBEPhoneNumberTypeVOICEMAIL;
|
||
}
|
||
|
||
BOOL isFixedLine = [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.fixedLine];
|
||
if (isFixedLine) {
|
||
if (metadata.sameMobileAndFixedLinePattern) {
|
||
return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
|
||
} else if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) {
|
||
return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
|
||
}
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* - param {string} nationalNumber
|
||
* - param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
|
||
* @return {boolean}
|
||
* @private
|
||
*/
|
||
- (BOOL)isNumberMatchingDesc:(NSString *)nationalNumber numberDesc:(NBPhoneNumberDesc *)numberDesc {
|
||
NSNumber *actualLength = [NSNumber numberWithUnsignedInteger:nationalNumber.length];
|
||
|
||
if (numberDesc.possibleLength.count > 0 &&
|
||
[numberDesc.possibleLength indexOfObject:actualLength] == NSNotFound) {
|
||
return NO;
|
||
}
|
||
|
||
return [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 copy];
|
||
NBPhoneMetaData *metadata =
|
||
[self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
|
||
if (metadata == nil ||
|
||
([NB_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] == NO &&
|
||
![countryCode isEqualToNumber:[self getCountryCodeForValidRegion:regionCode error:nil]])) {
|
||
// 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 ([NBMetadataHelper 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 = [NBMetadataHelper 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];
|
||
|
||
NBMetadataHelper *helper = self.helper;
|
||
|
||
for (NSUInteger i = 0; i < regionCodesCount; i++) {
|
||
NSString *regionCode = [regionCodes objectAtIndex:i];
|
||
NBPhoneMetaData *metadata = [helper getMetadataForRegion:regionCode];
|
||
|
||
if ([NBMetadataHelper 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 = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
|
||
return regionCodes == nil ? NB_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 = [NBMetadataHelper 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 {
|
||
NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
|
||
|
||
if (metadata == nil) {
|
||
NSDictionary *userInfo = [NSDictionary
|
||
dictionaryWithObject:[NSString stringWithFormat:@"Invalid region code:%@", regionCode]
|
||
forKey:NSLocalizedDescriptionKey];
|
||
if (error != NULL) {
|
||
(*error) = [NSError errorWithDomain:@"INVALID_REGION_CODE" code:0 userInfo:userInfo];
|
||
}
|
||
|
||
return @-1;
|
||
}
|
||
|
||
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.helper 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 = [NBMetadataHelper
|
||
regionCodeFromCountryCode:[NSNumber numberWithUnsignedInteger: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;
|
||
}
|
||
|
||
number = NormalizeNonBreakingSpace(number);
|
||
|
||
/** @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 possible lengths for this region, based on the metadata
|
||
* being passed in, and determine whether it matches, or is too short or too long.
|
||
*/
|
||
- (NBEValidationResult)validateNumberLength:(NSString *)number
|
||
metadata:(NBPhoneMetaData *)metadata {
|
||
return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeUNKNOWN];
|
||
}
|
||
|
||
/**
|
||
* Helper method to check a number against possible lengths for this number type, and determine
|
||
* whether it matches, or is too short or too long.
|
||
*/
|
||
- (NBEValidationResult)validateNumberLength:(NSString *)number
|
||
metadata:(NBPhoneMetaData *)metadata
|
||
type:(NBEPhoneNumberType)type {
|
||
NBPhoneNumberDesc *descForType = [self getNumberDescByType:metadata type:type];
|
||
// There should always be "possibleLengths" set for every element.
|
||
// For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
|
||
// as the parent, this is missing, so we fall back to the general desc (where no numbers of the
|
||
// type exist at all, there is one possible length (-1) which is guaranteed not to match the
|
||
// length of any real phone number).
|
||
NSArray<NSNumber *> *possibleLengths = [descForType.possibleLength count] == 0
|
||
? metadata.generalDesc.possibleLength
|
||
: descForType.possibleLength;
|
||
NSArray<NSNumber *> *localLengths = descForType.possibleLengthLocalOnly;
|
||
|
||
if (type == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE) {
|
||
if ([self descHasPossibleNumberData:[self getNumberDescByType:metadata
|
||
type:NBEPhoneNumberTypeFIXED_LINE]]) {
|
||
// The rare case has been encountered where no fixedLine data is available (true for some
|
||
// non-geographical entities), so we just check mobile.
|
||
return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeMOBILE];
|
||
} else {
|
||
NBPhoneNumberDesc *mobileDesc =
|
||
[self getNumberDescByType:metadata type:NBEPhoneNumberTypeMOBILE];
|
||
if ([self descHasPossibleNumberData:mobileDesc]) {
|
||
// Merge the mobile data in if there was any. We have to make a copy to do this.
|
||
// Note that when adding the possible lengths from mobile, we have to again check they
|
||
// aren't empty since if they are this indicates they are the same as the general desc and
|
||
// should be obtained from there.
|
||
NSArray *combinedArray =
|
||
[possibleLengths arrayByAddingObjectsFromArray:[mobileDesc.possibleLength count] == 0
|
||
? metadata.generalDesc.possibleLength
|
||
: mobileDesc.possibleLength];
|
||
|
||
// The current list is sorted; we need to merge in the new list and re-sort (duplicates
|
||
// are okay). Sorting isn't so expensive because the lists are very small.
|
||
possibleLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];
|
||
|
||
if (![localLengths count]) {
|
||
localLengths = mobileDesc.possibleLengthLocalOnly;
|
||
} else {
|
||
NSArray *combinedArray =
|
||
[localLengths arrayByAddingObjectsFromArray:mobileDesc.possibleLengthLocalOnly];
|
||
localLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// If the type is not supported at all (indicated by the possible lengths containing -1 at this
|
||
// point) we return invalid length.
|
||
if ([possibleLengths.firstObject isEqualToNumber:@(-1)]) {
|
||
return NBEValidationResultINVALID_LENGTH;
|
||
}
|
||
|
||
NSNumber *actualLength = @(number.length);
|
||
// This is safe because there is never an overlap beween the possible lengths and the local-only
|
||
// lengths; this is checked at build time.
|
||
if ([localLengths containsObject:actualLength]) {
|
||
return NBEValidationResultIS_POSSIBLE_LOCAL_ONLY;
|
||
}
|
||
|
||
NSNumber *minimumLength = possibleLengths.firstObject;
|
||
NSComparisonResult comparisionResult = [minimumLength compare:actualLength];
|
||
|
||
if (comparisionResult == NSOrderedSame) {
|
||
return NBEValidationResultIS_POSSIBLE;
|
||
} else if (comparisionResult == NSOrderedDescending) {
|
||
return NBEValidationResultTOO_SHORT;
|
||
} else if ([possibleLengths.lastObject compare:actualLength] == NSOrderedAscending) {
|
||
return NBEValidationResultTOO_LONG;
|
||
}
|
||
|
||
// We skip the first element; we've already checked it.
|
||
NSArray *possibleLengthsSubarray =
|
||
[possibleLengths subarrayWithRange:NSMakeRange(1, possibleLengths.count - 1)];
|
||
return [possibleLengthsSubarray containsObject:actualLength] ? NBEValidationResultIS_POSSIBLE
|
||
: NBEValidationResultINVALID_LENGTH;
|
||
}
|
||
|
||
/**
|
||
* 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} number
|
||
* - param {i18n.phonenumbers.PhoneNumberDesc} phoneNumberDesc
|
||
* - return {i18n.phonenumbers.PhoneNumberUtil.ValidationResult}
|
||
* @private
|
||
*/
|
||
- (NBEValidationResult)testNumberLength:(NSString *)number
|
||
desc:(NBPhoneNumberDesc *)phoneNumberDesc {
|
||
NSArray *possibleLengths = phoneNumberDesc.possibleLength;
|
||
NSArray *localLengths = phoneNumberDesc.possibleLengthLocalOnly;
|
||
|
||
NSUInteger actualLength = number.length;
|
||
|
||
if ([localLengths containsObject:@(actualLength)]) {
|
||
return NBEValidationResultIS_POSSIBLE;
|
||
}
|
||
|
||
// There should always be "possibleLengths" set for every element. This will
|
||
// be a build-time check once ShortNumberMetadata.xml is migrated to contain
|
||
// this information as well.
|
||
NSNumber *minimumLength = possibleLengths[0];
|
||
if (minimumLength.unsignedIntegerValue == actualLength) {
|
||
return NBEValidationResultIS_POSSIBLE;
|
||
} else if (minimumLength.unsignedIntegerValue > actualLength) {
|
||
return NBEValidationResultTOO_SHORT;
|
||
} else if (possibleLengths.count - 1 < possibleLengths.count) {
|
||
if (((NSNumber *)possibleLengths[possibleLengths.count - 1]).integerValue < actualLength) {
|
||
return NBEValidationResultTOO_LONG;
|
||
}
|
||
}
|
||
// Note that actually the number is not too long if possible_lengths does not
|
||
// contain the length: we know it is less than the highest possible number
|
||
// length, and higher than the lowest possible number length. However, we
|
||
// don't currently have an enum to express this, so we return TOO_LONG in the
|
||
// short-term.
|
||
// We skip the first element since we've already checked it.
|
||
return [possibleLengths containsObject:@(actualLength)] ? NBEValidationResultIS_POSSIBLE
|
||
: NBEValidationResultTOO_LONG;
|
||
}
|
||
|
||
/**
|
||
* 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 = NBEValidationResultUNKNOWN;
|
||
@try {
|
||
res = [self isPossibleNumberWithReason: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;
|
||
}
|
||
|
||
- (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];
|
||
return [self testNumberLength:nationalNumber desc:metadata.generalDesc];
|
||
}
|
||
|
||
/**
|
||
* 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 {
|
||
number = NormalizeNonBreakingSpace(number);
|
||
|
||
BOOL res =
|
||
[self isPossibleNumber:[self parse:number defaultRegion:regionDialingFrom error:error]];
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 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 {
|
||
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 {
|
||
fullNumber = NormalizeNonBreakingSpace(fullNumber);
|
||
|
||
if ((fullNumber.length == 0) || ([[fullNumber substringToIndex:1] isEqualToString:@"0"])) {
|
||
// Country codes do not begin with a '0'.
|
||
return @0;
|
||
}
|
||
|
||
NSUInteger numberLength = fullNumber.length;
|
||
NSUInteger maxCountryCode = MAX_LENGTH_COUNTRY_CODE_;
|
||
|
||
if ([fullNumber hasPrefix:@"+"]) {
|
||
maxCountryCode = MAX_LENGTH_COUNTRY_CODE_ + 1;
|
||
}
|
||
|
||
for (NSUInteger i = 1; i <= maxCountryCode && i <= numberLength; ++i) {
|
||
NSString *subNumber = [fullNumber substringWithRange:NSMakeRange(0, i)];
|
||
NSNumber *potentialCountryCode = [NSNumber numberWithInteger:[subNumber integerValue]];
|
||
|
||
NSArray *regionCodes = [NBMetadataHelper 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;
|
||
}
|
||
|
||
/**
|
||
* Convenience method to get a list of what regions the library has metadata
|
||
* for.
|
||
* @return {!Array.<string>} region codes supported by the library.
|
||
*/
|
||
|
||
- (NSArray *)getSupportedRegions {
|
||
NSArray *allKeys = [[NBMetadataHelper CCode2CNMap] allKeys];
|
||
NSPredicate *predicateIsNaN =
|
||
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
|
||
return isNan(evaluatedObject);
|
||
}];
|
||
|
||
NSArray *supportedRegions = [allKeys filteredArrayUsingPredicate:predicateIsNaN];
|
||
return supportedRegions;
|
||
}
|
||
|
||
/*
|
||
i18n.phonenumbers.PhoneNumberUtil.prototype.getSupportedRegions = function() {
|
||
return goog.array.filter(
|
||
Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
|
||
function(regionCode) {
|
||
return isNaN(regionCode);
|
||
});
|
||
};
|
||
*/
|
||
|
||
/**
|
||
* Convenience method to get a list of what global network calling codes the
|
||
* library has metadata for.
|
||
* @return {!Array.<number>} global network calling codes supported by the
|
||
* library.
|
||
*/
|
||
/*
|
||
i18n.phonenumbers.PhoneNumberUtil.prototype.
|
||
getSupportedGlobalNetworkCallingCodes = function() {
|
||
var callingCodesAsStrings = goog.array.filter(
|
||
Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
|
||
function(regionCode) {
|
||
return !isNaN(regionCode);
|
||
});
|
||
return goog.array.map(callingCodesAsStrings,
|
||
function(callingCode) {
|
||
return parseInt(callingCode, 10);
|
||
});
|
||
};
|
||
*/
|
||
|
||
/**
|
||
* 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 || 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 numberWithInteger:countryCodeSource];
|
||
}
|
||
|
||
if (countryCodeSource != NBECountryCodeSourceFROM_DEFAULT_COUNTRY) {
|
||
if (fullNumber.length <= MIN_LENGTH_FOR_NSN_) {
|
||
NSDictionary *userInfo = [NSDictionary
|
||
dictionaryWithObject:[NSString stringWithFormat:@"TOO_SHORT_AFTER_IDD:%@", fullNumber]
|
||
forKey:NSLocalizedDescriptionKey];
|
||
if (error != NULL) {
|
||
(*error) = [NSError errorWithDomain:@"TOO_SHORT_AFTER_IDD" code:0 userInfo:userInfo];
|
||
}
|
||
return @0;
|
||
}
|
||
|
||
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.
|
||
NSDictionary *userInfo =
|
||
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@",
|
||
potentialCountryCode]
|
||
forKey:NSLocalizedDescriptionKey];
|
||
if (error != NULL) {
|
||
(*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
|
||
}
|
||
|
||
return @0;
|
||
} 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];
|
||
// 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 testNumberLength:fullNumber desc:generalDesc] == NBEValidationResultTOO_LONG) {
|
||
(*nationalNumber) = [(*nationalNumber) stringByAppendingString:potentialNationalNumberStr];
|
||
if (keepRawInput) {
|
||
(*phoneNumber).countryCodeSource =
|
||
[NSNumber numberWithInteger:NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN];
|
||
}
|
||
(*phoneNumber).countryCode = defaultCountryCode;
|
||
return defaultCountryCode;
|
||
}
|
||
}
|
||
}
|
||
// No country calling code present.
|
||
(*phoneNumber).countryCode = @0;
|
||
return @0;
|
||
}
|
||
|
||
/**
|
||
* Returns true if there is any possible number data set for a particular PhoneNumberDesc.
|
||
*/
|
||
- (BOOL)descHasPossibleNumberData:(NBPhoneNumberDesc *)desc {
|
||
// If this is empty, it means numbers of this type inherit from the "general desc" -> the value
|
||
// "-1" means that no numbers exist for this type.
|
||
return [desc.possibleLength count] != 1 ||
|
||
![[desc.possibleLength firstObject] isEqualToNumber:@(-1)];
|
||
}
|
||
|
||
/**
|
||
* 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];
|
||
|
||
NSArray *matchedGroups =
|
||
[_CAPTURING_DIGIT_PATTERN 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 {
|
||
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:LEADING_PLUS_CHARS_PATTERN]) {
|
||
(*numberStr) =
|
||
[self replaceStringByRegex:(*numberStr)regex:LEADING_PLUS_CHARS_PATTERN withTemplate:@""];
|
||
// Can now normalize the rest of the number since we've consumed the '+'
|
||
// sign at the start.
|
||
(*numberStr) = [self normalize:(*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 {
|
||
if (number == NULL) {
|
||
return NO;
|
||
}
|
||
|
||
NSString *numberStr = [(*number)copy];
|
||
NSUInteger numberLength = numberStr.length;
|
||
NSString *possibleNationalPrefix = metadata.nationalPrefixForParsing;
|
||
|
||
if (numberLength == 0 || [NBMetadataHelper 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 =
|
||
[self 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 ||
|
||
[NBMetadataHelper 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 ([NBMetadataHelper hasValue:nationalNumberRule] &&
|
||
[self matchesEntirely:nationalNumberRule string:numberStr] &&
|
||
[self matchesEntirely:nationalNumberRule string:transformedNumber] == NO) {
|
||
return NO;
|
||
}
|
||
|
||
if ((noTransform && numOfGroups > 0 && [NBMetadataHelper hasValue:firstMatchStringWithGroup]) ||
|
||
(!noTransform && numOfGroups > 1)) {
|
||
if (carrierCode != NULL && (*carrierCode) != nil) {
|
||
(*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchStringWithGroup];
|
||
}
|
||
} else if ((noTransform && numOfGroups > 0 && [NBMetadataHelper 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];
|
||
int mStart = [self stringPositionByRegex:numberStr regex: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 matchFirstByRegex:numberStr regex: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: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
|
||
error:(NSError **)error {
|
||
NSError *anError = nil;
|
||
NBPhoneNumber *phoneNumber = [self parseHelper:numberToParse
|
||
defaultRegion:defaultRegion
|
||
keepRawInput:NO
|
||
checkRegion:YES
|
||
error:&anError];
|
||
|
||
if (anError != nil) {
|
||
if (error != NULL) {
|
||
(*error) = [self errorWithObject:anError.description withDomain:anError.domain];
|
||
}
|
||
}
|
||
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 {
|
||
numberToParse = NormalizeNonBreakingSpace(numberToParse);
|
||
|
||
NSString *defaultRegion = nil;
|
||
#if TARGET_OS_IOS
|
||
defaultRegion = [self countryCodeByCarrier];
|
||
#else
|
||
defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
|
||
#endif
|
||
if ([NB_UNKNOWN_REGION isEqualToString:defaultRegion]) {
|
||
// get region from device as a failover (e.g. iPad)
|
||
NSLocale *currentLocale = [NSLocale currentLocale];
|
||
defaultRegion = [currentLocale objectForKey:NSLocaleCountryCode];
|
||
}
|
||
|
||
return [self parse:numberToParse defaultRegion:defaultRegion error:error];
|
||
}
|
||
|
||
#if TARGET_OS_IOS
|
||
|
||
static CTTelephonyNetworkInfo *_telephonyNetworkInfo;
|
||
|
||
- (CTTelephonyNetworkInfo *)telephonyNetworkInfo {
|
||
// cache telephony network info;
|
||
// CTTelephonyNetworkInfo objects are unnecessarily created for every call to
|
||
// parseWithPhoneCarrierRegion:error: when in reality this information not change while an app
|
||
// lives in memory real-world performance test while parsing 93 phone numbers: before change:
|
||
// 126ms after change: 32ms using static instance prevents deallocation crashes due to ios bug
|
||
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
_telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init];
|
||
});
|
||
|
||
return _telephonyNetworkInfo;
|
||
}
|
||
|
||
- (NSString *)countryCodeByCarrier {
|
||
NSString *isoCode = [[self.telephonyNetworkInfo subscriberCellularProvider] isoCountryCode];
|
||
|
||
// The 2nd part of the if is working around an iOS 7 bug
|
||
// If the SIM card is missing, iOS 7 returns an empty string instead of nil
|
||
if (isoCode.length == 0) {
|
||
isoCode = NB_UNKNOWN_REGION;
|
||
}
|
||
|
||
return isoCode;
|
||
}
|
||
|
||
#endif
|
||
|
||
/**
|
||
* 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 {
|
||
if ([self isValidRegionCode:defaultRegion] == NO) {
|
||
if (numberToParse.length > 0 && [numberToParse hasPrefix:@"+"] == NO) {
|
||
NSDictionary *userInfo = [NSDictionary
|
||
dictionaryWithObject:[NSString stringWithFormat:@"Invalid country code:%@", numberToParse]
|
||
forKey:NSLocalizedDescriptionKey];
|
||
if (error != NULL) {
|
||
(*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
|
||
}
|
||
}
|
||
}
|
||
return [self parseHelper:numberToParse
|
||
defaultRegion:defaultRegion
|
||
keepRawInput:YES
|
||
checkRegion:YES
|
||
error:error];
|
||
}
|
||
|
||
/**
|
||
* A helper function to set the values related to leading zeros in a
|
||
* PhoneNumber.
|
||
*
|
||
* - param {string} nationalNumber the number we are parsing.
|
||
* - param {i18n.phonenumbers.PhoneNumber} phoneNumber a phone number proto
|
||
* buffer to fill in.
|
||
* @private
|
||
*/
|
||
- (void)setItalianLeadingZerosForPhoneNumber:(NSString *)nationalNumber
|
||
phoneNumber:(NBPhoneNumber *)phoneNumber {
|
||
if (nationalNumber.length > 1 && [nationalNumber hasPrefix:@"0"]) {
|
||
phoneNumber.italianLeadingZero = YES;
|
||
NSInteger numberOfLeadingZeros = 1;
|
||
// Note that if the national number is all "0"s, the last "0" is not counted
|
||
// as a leading zero.
|
||
while (numberOfLeadingZeros < nationalNumber.length - 1 &&
|
||
[[nationalNumber substringWithRange:NSMakeRange(numberOfLeadingZeros, 1)]
|
||
isEqualToString:@"0"]) {
|
||
numberOfLeadingZeros++;
|
||
}
|
||
if (numberOfLeadingZeros != 1) {
|
||
phoneNumber.numberOfLeadingZeros = @(numberOfLeadingZeros);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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 {
|
||
numberToParse = NormalizeNonBreakingSpace(numberToParse);
|
||
|
||
if (numberToParse == nil) {
|
||
if (error != NULL) {
|
||
(*error) = [self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", numberToParse]
|
||
withDomain:@"NOT_A_NUMBER"];
|
||
}
|
||
|
||
return nil;
|
||
} else if (numberToParse.length > MAX_INPUT_STRING_LENGTH_) {
|
||
if (error != NULL) {
|
||
(*error) = [self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", numberToParse]
|
||
withDomain:@"TOO_LONG"];
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
NSString *nationalNumber = @"";
|
||
[self buildNationalNumberForParsing:numberToParse nationalNumber:&nationalNumber];
|
||
|
||
if ([self isViablePhoneNumber:nationalNumber] == NO) {
|
||
if (error != NULL) {
|
||
(*error) =
|
||
[self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", nationalNumber]
|
||
withDomain:@"NOT_A_NUMBER"];
|
||
}
|
||
|
||
return 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) {
|
||
if (error != NULL) {
|
||
(*error) = [self
|
||
errorWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", defaultRegion]
|
||
withDomain:@"INVALID_COUNTRY_CODE"];
|
||
}
|
||
|
||
return 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.helper 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 = nil;
|
||
NSString *nationalNumberStr = [nationalNumber copy];
|
||
|
||
{
|
||
NSError *anError = nil;
|
||
countryCode = [self maybeExtractCountryCode:nationalNumberStr
|
||
metadata:regionMetadata
|
||
nationalNumber:&normalizedNationalNumber
|
||
keepRawInput:keepRawInput
|
||
phoneNumber:&phoneNumber
|
||
error:&anError];
|
||
|
||
if (anError != nil) {
|
||
if ([anError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] &&
|
||
[self stringPositionByRegex:nationalNumberStr regex:LEADING_PLUS_CHARS_PATTERN] >= 0) {
|
||
// Strip the plus-char, and try again.
|
||
NSError *aNestedError = nil;
|
||
nationalNumberStr = [self replaceStringByRegex:nationalNumberStr
|
||
regex:LEADING_PLUS_CHARS_PATTERN
|
||
withTemplate:@""];
|
||
countryCode = [self maybeExtractCountryCode:nationalNumberStr
|
||
metadata:regionMetadata
|
||
nationalNumber:&normalizedNationalNumber
|
||
keepRawInput:keepRawInput
|
||
phoneNumber:&phoneNumber
|
||
error:&aNestedError];
|
||
if ([countryCode isEqualToNumber:@0]) {
|
||
if (error != NULL)
|
||
(*error) = [self errorWithObject:anError.description withDomain:anError.domain];
|
||
|
||
return nil;
|
||
}
|
||
} else {
|
||
if (error != NULL)
|
||
(*error) = [self errorWithObject:anError.description withDomain:anError.domain];
|
||
|
||
return nil;
|
||
}
|
||
}
|
||
}
|
||
|
||
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_) {
|
||
if (error != NULL) {
|
||
(*error) = [self
|
||
errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
|
||
withDomain:@"TOO_SHORT_NSN"];
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
if (regionMetadata != nil) {
|
||
NSString *carrierCode = @"";
|
||
NSString *potentialNationalNumber = [normalizedNationalNumber copy];
|
||
[self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber
|
||
metadata:regionMetadata
|
||
carrierCode:&carrierCode];
|
||
NBEValidationResult validationResult =
|
||
[self validateNumberLength:potentialNationalNumber metadata:regionMetadata];
|
||
if (validationResult != NBEValidationResultTOO_SHORT &&
|
||
validationResult != NBEValidationResultIS_POSSIBLE_LOCAL_ONLY &&
|
||
validationResult != NBEValidationResultINVALID_LENGTH) {
|
||
normalizedNationalNumber = potentialNationalNumber;
|
||
if (keepRawInput) {
|
||
phoneNumber.preferredDomesticCarrierCode = [carrierCode copy];
|
||
}
|
||
}
|
||
}
|
||
|
||
NSString *normalizedNationalNumberStr = [normalizedNationalNumber copy];
|
||
|
||
NSUInteger lengthOfNationalNumber = normalizedNationalNumberStr.length;
|
||
if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_) {
|
||
if (error != NULL) {
|
||
(*error) = [self
|
||
errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
|
||
withDomain:@"TOO_SHORT_NSN"];
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
if (lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_) {
|
||
if (error != NULL) {
|
||
(*error) =
|
||
[self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", normalizedNationalNumber]
|
||
withDomain:@"TOO_LONG"];
|
||
}
|
||
|
||
return nil;
|
||
}
|
||
|
||
[self setItalianLeadingZerosForPhoneNumber:normalizedNationalNumberStr phoneNumber:phoneNumber];
|
||
|
||
phoneNumber.nationalNumber =
|
||
[NSNumber numberWithLongLong:[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;
|
||
|
||
NSMutableString *result = [[NSMutableString alloc] init];
|
||
|
||
int 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);
|
||
[result appendString:[numberToParse substringWithRange:subRange]];
|
||
} else {
|
||
[result appendString:[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)];
|
||
[result appendString: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.)
|
||
[result appendString:[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 = [result copy];
|
||
int indexOfIsdn = [self indexOfStringByString:nationalNumberStr target:RFC3966_ISDN_SUBADDRESS];
|
||
if (indexOfIsdn > 0) {
|
||
NSRange range = NSMakeRange(0, indexOfIsdn);
|
||
result = [[NSMutableString alloc] initWithString:[nationalNumberStr substringWithRange:range]];
|
||
}
|
||
// 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.
|
||
|
||
*nationalNumber = [result copy];
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
NSError *anError;
|
||
firstNumber = [self parse:firstNumberIn defaultRegion:NB_UNKNOWN_REGION error:&anError];
|
||
|
||
if (anError != nil) {
|
||
if ([anError.domain 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 != NB_UNKNOWN_REGION) {
|
||
NSError *aNestedError;
|
||
firstNumber =
|
||
[self parse:firstNumberIn defaultRegion:secondNumberRegion error:&aNestedError];
|
||
|
||
if (aNestedError != nil) {
|
||
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.
|
||
NSError *aNestedError;
|
||
firstNumber = [self parseHelper:firstNumberIn
|
||
defaultRegion:nil
|
||
keepRawInput:NO
|
||
checkRegion:NO
|
||
error:&aNestedError];
|
||
if (aNestedError != nil) {
|
||
return NBEMatchTypeNOT_A_NUMBER;
|
||
}
|
||
}
|
||
} else {
|
||
firstNumber = [firstNumberIn copy];
|
||
}
|
||
|
||
if ([secondNumberIn isKindOfClass:[NSString class]]) {
|
||
NSError *parseError;
|
||
secondNumber = [self parse:secondNumberIn defaultRegion:NB_UNKNOWN_REGION error:&parseError];
|
||
if (parseError != nil) {
|
||
if ([parseError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) {
|
||
return NBEMatchTypeNOT_A_NUMBER;
|
||
}
|
||
return [self isNumberMatch:secondNumberIn second:firstNumber];
|
||
} else {
|
||
return [self isNumberMatch:firstNumberIn second:secondNumber];
|
||
}
|
||
} 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 ([NBMetadataHelper hasValue:firstNumber.extension] &&
|
||
[NBMetadataHelper 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.helper 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 isEqualToString:@"NA"]) {
|
||
return NO;
|
||
}
|
||
|
||
NSError *error = nil;
|
||
NSRegularExpression *currentPattern =
|
||
[self entireRegularExpressionWithPattern:regex options:0 error:&error];
|
||
NSRange stringRange = NSMakeRange(0, str.length);
|
||
NSTextCheckingResult *matchResult =
|
||
[currentPattern firstMatchInString:str options:NSMatchingAnchored range:stringRange];
|
||
|
||
if (matchResult != nil) {
|
||
BOOL matchIsEntireString = NSEqualRanges(matchResult.range, stringRange);
|
||
if (matchIsEntireString) {
|
||
return YES;
|
||
}
|
||
}
|
||
|
||
return NO;
|
||
}
|
||
|
||
@end
|