12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094 |
- //
- // 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
|