NBPhoneNumberUtil.m 175 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094
  1. //
  2. // NBPhoneNumberUtil.m
  3. // libPhoneNumber
  4. //
  5. // Created by tabby on 2015. 2. 8..
  6. // Copyright (c) 2015년 ohtalk.me. All rights reserved.
  7. //
  8. #import "NBPhoneNumberUtil.h"
  9. #import <math.h>
  10. #import "NBMetadataHelper.h"
  11. #import "NBNumberFormat.h"
  12. #import "NBPhoneMetaData.h"
  13. #import "NBPhoneNumber.h"
  14. #import "NBPhoneNumberDefines.h"
  15. #import "NBPhoneNumberDesc.h"
  16. #import "NBRegExMatcher.h"
  17. #if TARGET_OS_IOS
  18. #import <CoreTelephony/CTCarrier.h>
  19. #import <CoreTelephony/CTTelephonyNetworkInfo.h>
  20. #endif
  21. static NSString *NormalizeNonBreakingSpace(NSString *aString) {
  22. return [aString stringByReplacingOccurrencesOfString:NB_NON_BREAKING_SPACE withString:@" "];
  23. }
  24. static BOOL isNan(NSString *sourceString) {
  25. static dispatch_once_t onceToken;
  26. static NSCharacterSet *nonDecimalCharacterSet;
  27. dispatch_once(&onceToken, ^{
  28. nonDecimalCharacterSet = [[NSMutableCharacterSet decimalDigitCharacterSet] invertedSet];
  29. });
  30. // Return YES if the sourceString doesn't have any characters that can be represented as a Float.
  31. return !([sourceString rangeOfCharacterFromSet:nonDecimalCharacterSet].location == NSNotFound);
  32. }
  33. #pragma mark - NBPhoneNumberUtil interface -
  34. @interface NBPhoneNumberUtil ()
  35. @property(nonatomic, strong) NSLock *entireStringCacheLock;
  36. @property(nonatomic, strong) NSMutableDictionary *entireStringRegexCache;
  37. @property(nonatomic, strong) NSLock *lockPatternCache;
  38. @property(nonatomic, strong) NSMutableDictionary *regexPatternCache;
  39. @property(nonatomic, strong) NSRegularExpression *CAPTURING_DIGIT_PATTERN;
  40. @property(nonatomic, strong) NSRegularExpression *VALID_ALPHA_PHONE_PATTERN;
  41. @property(nonatomic, strong, readwrite) NBMetadataHelper *helper;
  42. @property(nonatomic, strong, readwrite) NBRegExMatcher *matcher;
  43. #if TARGET_OS_IOS
  44. @property(nonatomic, readonly) CTTelephonyNetworkInfo *telephonyNetworkInfo;
  45. #endif
  46. @end
  47. @implementation NBPhoneNumberUtil
  48. #pragma mark - Static Int variables -
  49. const static NSUInteger NANPA_COUNTRY_CODE_ = 1;
  50. const static NSUInteger MIN_LENGTH_FOR_NSN_ = 2;
  51. const static NSUInteger MAX_LENGTH_FOR_NSN_ = 16;
  52. const static NSUInteger MAX_LENGTH_COUNTRY_CODE_ = 3;
  53. const static NSUInteger MAX_INPUT_STRING_LENGTH_ = 250;
  54. #pragma mark - Static String variables -
  55. static NSString *VALID_PUNCTUATION =
  56. @"-x‐-―−ー--/ ­​⁠ ()()[].\\[\\]/~⁓∼~";
  57. static NSString *COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = @"3";
  58. static NSString *PLUS_SIGN = @"+";
  59. static NSString *STAR_SIGN = @"*";
  60. static NSString *RFC3966_EXTN_PREFIX = @";ext=";
  61. static NSString *RFC3966_PREFIX = @"tel:";
  62. static NSString *RFC3966_PHONE_CONTEXT = @";phone-context=";
  63. static NSString *RFC3966_ISDN_SUBADDRESS = @";isub=";
  64. static NSString *DEFAULT_EXTN_PREFIX = @" ext. ";
  65. static NSString *VALID_ALPHA = @"A-Za-z";
  66. #pragma mark - Static regular expression strings -
  67. static NSString *NON_DIGITS_PATTERN = @"\\D+";
  68. static NSString *CC_PATTERN = @"\\$CC";
  69. static NSString *FIRST_GROUP_PATTERN = @"(\\$\\d)";
  70. static NSString *FIRST_GROUP_ONLY_PREFIX_PATTERN = @"^\\(?\\$1\\)?";
  71. static NSString *NP_PATTERN = @"\\$NP";
  72. static NSString *FG_PATTERN = @"\\$FG";
  73. static NSString *VALID_ALPHA_PHONE_PATTERN_STRING = @"(?:.*?[A-Za-z]){3}.*";
  74. static NSString *UNIQUE_INTERNATIONAL_PREFIX = @"[\\d]+(?:[~\\u2053\\u223C\\uFF5E][\\d]+)?";
  75. static NSString *LEADING_PLUS_CHARS_PATTERN;
  76. static NSString *EXTN_PATTERN;
  77. static NSString *SEPARATOR_PATTERN;
  78. static NSString *VALID_PHONE_NUMBER_PATTERN;
  79. static NSString *VALID_START_CHAR_PATTERN;
  80. static NSString *UNWANTED_END_CHAR_PATTERN;
  81. static NSString *SECOND_NUMBER_START_PATTERN;
  82. static NSDictionary *ALL_NORMALIZATION_MAPPINGS;
  83. static NSDictionary *DIALLABLE_CHAR_MAPPINGS;
  84. static NSDictionary *ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
  85. // Map of country calling codes that use a mobile token before the area code. One example of when
  86. // this is relevant is when determining the length of the national destination code, which should
  87. // be the length of the area code plus the length of the mobile token.
  88. static NSDictionary<NSNumber *, NSString *> *MOBILE_TOKEN_MAPPINGS;
  89. static NSDictionary *DIGIT_MAPPINGS;
  90. static NSArray *GEO_MOBILE_COUNTRIES;
  91. #pragma mark - Deprecated methods
  92. + (NBPhoneNumberUtil *)sharedInstance {
  93. static NBPhoneNumberUtil *sharedOnceInstance = nil;
  94. static dispatch_once_t onceToken;
  95. dispatch_once(&onceToken, ^{
  96. sharedOnceInstance = [[self alloc] init];
  97. });
  98. return sharedOnceInstance;
  99. }
  100. #pragma mark - NSError
  101. - (NSError *)errorWithObject:(id)obj withDomain:(NSString *)domain {
  102. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:obj forKey:NSLocalizedDescriptionKey];
  103. NSError *error = [NSError errorWithDomain:domain code:0 userInfo:userInfo];
  104. return error;
  105. }
  106. - (NSRegularExpression *)entireRegularExpressionWithPattern:(NSString *)regexPattern
  107. options:(NSRegularExpressionOptions)options
  108. error:(NSError **)error {
  109. [_entireStringCacheLock lock];
  110. @try {
  111. if (!_entireStringRegexCache) {
  112. _entireStringRegexCache = [[NSMutableDictionary alloc] init];
  113. }
  114. NSRegularExpression *regex = [_entireStringRegexCache objectForKey:regexPattern];
  115. if (!regex) {
  116. NSString *finalRegexString = regexPattern;
  117. if ([regexPattern rangeOfString:@"^"].location == NSNotFound) {
  118. finalRegexString = [NSString stringWithFormat:@"^(?:%@)$", regexPattern];
  119. }
  120. regex = [self regularExpressionWithPattern:finalRegexString options:0 error:error];
  121. [_entireStringRegexCache setObject:regex forKey:regexPattern];
  122. }
  123. return regex;
  124. } @finally {
  125. [_entireStringCacheLock unlock];
  126. }
  127. }
  128. - (NSRegularExpression *)regularExpressionWithPattern:(NSString *)pattern
  129. options:(NSRegularExpressionOptions)options
  130. error:(NSError **)error {
  131. [_lockPatternCache lock];
  132. @try {
  133. if (!_regexPatternCache) {
  134. _regexPatternCache = [[NSMutableDictionary alloc] init];
  135. }
  136. NSRegularExpression *regex = [_regexPatternCache objectForKey:pattern];
  137. if (!regex) {
  138. regex =
  139. [NSRegularExpression regularExpressionWithPattern:pattern options:options error:error];
  140. [_regexPatternCache setObject:regex forKey:pattern];
  141. }
  142. return regex;
  143. } @finally {
  144. [_lockPatternCache unlock];
  145. }
  146. }
  147. - (NSMutableArray *)componentsSeparatedByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  148. NSString *replacedString =
  149. [self replaceStringByRegex:sourceString regex:pattern withTemplate:@"<SEP>"];
  150. NSMutableArray *resArray = [[replacedString componentsSeparatedByString:@"<SEP>"] mutableCopy];
  151. [resArray removeObject:@""];
  152. return resArray;
  153. }
  154. - (int)stringPositionByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  155. if (sourceString == nil || sourceString.length <= 0 || pattern == nil || pattern.length <= 0) {
  156. return -1;
  157. }
  158. NSError *error = nil;
  159. NSRegularExpression *currentPattern =
  160. [self regularExpressionWithPattern:pattern options:0 error:&error];
  161. NSArray *matches = [currentPattern matchesInString:sourceString
  162. options:0
  163. range:NSMakeRange(0, sourceString.length)];
  164. int foundPosition = -1;
  165. if (matches.count > 0) {
  166. NSTextCheckingResult *match = [matches objectAtIndex:0];
  167. return (int)match.range.location;
  168. }
  169. return foundPosition;
  170. }
  171. - (int)indexOfStringByString:(NSString *)sourceString target:(NSString *)targetString {
  172. NSRange finded = [sourceString rangeOfString:targetString];
  173. if (finded.location != NSNotFound) {
  174. return (int)finded.location;
  175. }
  176. return -1;
  177. }
  178. - (NSString *)replaceFirstStringByRegex:(NSString *)sourceString
  179. regex:(NSString *)pattern
  180. withTemplate:(NSString *)templateString {
  181. NSString *replacementResult = [sourceString copy];
  182. NSError *error = nil;
  183. NSRegularExpression *currentPattern =
  184. [self regularExpressionWithPattern:pattern options:0 error:&error];
  185. NSRange replaceRange =
  186. [currentPattern rangeOfFirstMatchInString:sourceString
  187. options:0
  188. range:NSMakeRange(0, sourceString.length)];
  189. if (replaceRange.location != NSNotFound) {
  190. replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy]
  191. options:0
  192. range:replaceRange
  193. withTemplate:templateString];
  194. }
  195. return replacementResult;
  196. }
  197. - (NSString *)replaceStringByRegex:(NSString *)sourceString
  198. regex:(NSString *)pattern
  199. withTemplate:(NSString *)templateString {
  200. NSError *error = nil;
  201. NSRegularExpression *currentPattern =
  202. [self regularExpressionWithPattern:pattern options:0 error:&error];
  203. NSArray *matches = [currentPattern matchesInString:sourceString
  204. options:0
  205. range:NSMakeRange(0, sourceString.length)];
  206. if ([matches count] == 1) {
  207. NSString *replacementResult;
  208. NSRange replaceRange =
  209. [currentPattern rangeOfFirstMatchInString:sourceString
  210. options:0
  211. range:NSMakeRange(0, sourceString.length)];
  212. if (replaceRange.location != NSNotFound) {
  213. replacementResult =
  214. [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy]
  215. options:0
  216. range:replaceRange
  217. withTemplate:templateString];
  218. } else {
  219. replacementResult = [sourceString copy];
  220. }
  221. return replacementResult;
  222. }
  223. if ([matches count] > 1) {
  224. return [currentPattern stringByReplacingMatchesInString:sourceString
  225. options:0
  226. range:NSMakeRange(0, sourceString.length)
  227. withTemplate:templateString];
  228. }
  229. return [sourceString copy];
  230. }
  231. - (NSTextCheckingResult *)matchFirstByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  232. NSError *error = nil;
  233. NSRegularExpression *currentPattern =
  234. [self regularExpressionWithPattern:pattern options:0 error:&error];
  235. NSArray *matches = [currentPattern matchesInString:sourceString
  236. options:0
  237. range:NSMakeRange(0, sourceString.length)];
  238. if ([matches count] > 0) return [matches objectAtIndex:0];
  239. return nil;
  240. }
  241. - (NSArray *)matchesByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  242. NSError *error = nil;
  243. NSRegularExpression *currentPattern =
  244. [self regularExpressionWithPattern:pattern options:0 error:&error];
  245. NSArray *matches = [currentPattern matchesInString:sourceString
  246. options:0
  247. range:NSMakeRange(0, sourceString.length)];
  248. return matches;
  249. }
  250. - (NSArray *)matchedStringByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  251. NSArray *matches = [self matchesByRegex:sourceString regex:pattern];
  252. NSMutableArray *matchString = [[NSMutableArray alloc] init];
  253. for (NSTextCheckingResult *match in matches) {
  254. NSString *curString = [sourceString substringWithRange:match.range];
  255. [matchString addObject:curString];
  256. }
  257. return matchString;
  258. }
  259. - (BOOL)isStartingStringByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  260. NSError *error = nil;
  261. NSRegularExpression *currentPattern =
  262. [self regularExpressionWithPattern:pattern options:0 error:&error];
  263. NSArray *matches = [currentPattern matchesInString:sourceString
  264. options:0
  265. range:NSMakeRange(0, sourceString.length)];
  266. for (NSTextCheckingResult *match in matches) {
  267. if (match.range.location == 0) {
  268. return YES;
  269. }
  270. }
  271. return NO;
  272. }
  273. - (NSString *)stringByReplacingOccurrencesString:(NSString *)sourceString
  274. withMap:(NSDictionary *)dicMap
  275. removeNonMatches:(BOOL)bRemove {
  276. NSMutableString *targetString = [[NSMutableString alloc] init];
  277. NSUInteger length = sourceString.length;
  278. for (NSUInteger i = 0; i < length; i++) {
  279. unichar oneChar = [sourceString characterAtIndex:i];
  280. NSString *keyString = [NSString stringWithCharacters:&oneChar length:1];
  281. NSString *mappedValue = [dicMap objectForKey:keyString];
  282. if (mappedValue != nil) {
  283. [targetString appendString:mappedValue];
  284. } else {
  285. if (bRemove == NO) {
  286. [targetString appendString:keyString];
  287. }
  288. }
  289. }
  290. return targetString;
  291. }
  292. - (BOOL)isAllDigits:(NSString *)sourceString {
  293. NSCharacterSet *nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
  294. NSRange r = [sourceString rangeOfCharacterFromSet:nonNumbers];
  295. return r.location == NSNotFound;
  296. }
  297. /**
  298. * Gets the national significant number of the a phone number. Note a national
  299. * significant number doesn't contain a national prefix or any formatting.
  300. *
  301. * - param {i18n.phonenumbers.PhoneNumber} number the phone number for which the
  302. * national significant number is needed.
  303. * @return {string} the national significant number of the PhoneNumber object
  304. * passed in.
  305. */
  306. - (NSString *)getNationalSignificantNumber:(NBPhoneNumber *)phoneNumber {
  307. // If leading zero(s) have been set, we prefix this now. Note this is not a
  308. // national prefix.
  309. NSString *nationalNumber = [phoneNumber.nationalNumber stringValue];
  310. if (phoneNumber.italianLeadingZero) {
  311. NSString *zeroNumbers =
  312. [@"" stringByPaddingToLength:phoneNumber.numberOfLeadingZeros.integerValue
  313. withString:@"0"
  314. startingAtIndex:0];
  315. return [NSString stringWithFormat:@"%@%@", zeroNumbers, nationalNumber];
  316. }
  317. return [phoneNumber.nationalNumber stringValue];
  318. }
  319. #pragma mark - Initializations -
  320. + (void)initialize {
  321. [super initialize];
  322. /**
  323. * Set of country calling codes that have geographically assigned mobile
  324. * numbers. This may not be complete; we add calling codes case by case, as we
  325. * find geographical mobile numbers or hear from user reports.
  326. *
  327. * @const
  328. * @type {!Array.<number>}
  329. * @private
  330. */
  331. // @[ Mexico, Argentina, Brazil ]
  332. GEO_MOBILE_COUNTRIES = @[ @52, @54, @55 ];
  333. }
  334. - (instancetype)init {
  335. self = [super init];
  336. if (self) {
  337. _lockPatternCache = [[NSLock alloc] init];
  338. _entireStringCacheLock = [[NSLock alloc] init];
  339. _helper = [[NBMetadataHelper alloc] init];
  340. _matcher = [[NBRegExMatcher alloc] init];
  341. [self initRegularExpressionSet];
  342. [self initNormalizationMappings];
  343. }
  344. return self;
  345. }
  346. - (void)initRegularExpressionSet {
  347. NSError *error = nil;
  348. if (!_CAPTURING_DIGIT_PATTERN) {
  349. _CAPTURING_DIGIT_PATTERN = [self
  350. regularExpressionWithPattern:[NSString stringWithFormat:@"([%@])", NB_VALID_DIGITS_STRING]
  351. options:0
  352. error:&error];
  353. }
  354. if (!_VALID_ALPHA_PHONE_PATTERN) {
  355. _VALID_ALPHA_PHONE_PATTERN =
  356. [self regularExpressionWithPattern:VALID_ALPHA_PHONE_PATTERN_STRING options:0 error:&error];
  357. }
  358. static dispatch_once_t onceToken;
  359. dispatch_once(
  360. &onceToken, ^{
  361. NSString *EXTN_PATTERNS_FOR_PARSING =
  362. @"(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ "
  363. @"\\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xxX##~~]|int|anexo|int)[:\\..]?["
  364. @" \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)$";
  365. LEADING_PLUS_CHARS_PATTERN = [NSString stringWithFormat:@"^[%@]+", NB_PLUS_CHARS];
  366. VALID_START_CHAR_PATTERN =
  367. [NSString stringWithFormat:@"[%@%@]", NB_PLUS_CHARS, NB_VALID_DIGITS_STRING];
  368. SECOND_NUMBER_START_PATTERN = @"[\\\\\\/] *x";
  369. UNWANTED_END_CHAR_PATTERN =
  370. [NSString stringWithFormat:@"[^%@%@#]+$", NB_VALID_DIGITS_STRING, VALID_ALPHA];
  371. EXTN_PATTERN = [NSString stringWithFormat:@"(?:%@)$", EXTN_PATTERNS_FOR_PARSING];
  372. SEPARATOR_PATTERN = [NSString stringWithFormat:@"[%@]+", VALID_PUNCTUATION];
  373. VALID_PHONE_NUMBER_PATTERN =
  374. @"^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x‐-―−ー--/ "
  375. @"­​⁠ ()()[].\\[\\]/~⁓∼~*]*[0-90-9٠-٩۰-۹]){3,}[-x‐-―−ー--/ "
  376. @"­​⁠ ()()[].\\[\\]/"
  377. @"~⁓∼~*A-Za-z0-90-9٠-٩۰-۹]*(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ "
  378. @"\\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xx##~~]|int|anexo|int)[:\\..]?[ "
  379. @" \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)?$";
  380. });
  381. }
  382. - (NSDictionary *)DIGIT_MAPPINGS {
  383. static dispatch_once_t onceToken;
  384. dispatch_once(&onceToken, ^{
  385. DIGIT_MAPPINGS = [NSDictionary
  386. dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
  387. @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
  388. // Fullwidth digit 0 to 9
  389. @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3",
  390. @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16",
  391. @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
  392. // Arabic-indic digit 0 to 9
  393. @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3",
  394. @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666",
  395. @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
  396. // Eastern-Arabic digit 0 to 9
  397. @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3",
  398. @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6",
  399. @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9",
  400. // BENGALI digit 0 to 9
  401. @"0", @"\u09E6", @"1", @"\u09E7", @"2", @"\u09E8", @"3",
  402. @"\u09E9", @"4", @"\u09EA", @"5", @"\u09EB", @"6", @"\u09EC",
  403. @"7", @"\u09ED", @"8", @"\u09EE", @"9", @"\u09EF",
  404. // DEVANAGARI digit 0 to 9
  405. @"0", @"\u0966", @"1", @"\u0967", @"2", @"\u0968", @"3",
  406. @"\u0969", @"4", @"\u096A", @"5", @"\u096B", @"6", @"\u096C",
  407. @"7", @"\u096D", @"8", @"\u096E", @"9", @"\u096F", nil];
  408. });
  409. return DIGIT_MAPPINGS;
  410. }
  411. - (void)initNormalizationMappings {
  412. static dispatch_once_t onceToken;
  413. dispatch_once(&onceToken, ^{
  414. DIALLABLE_CHAR_MAPPINGS = [NSDictionary
  415. dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
  416. @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
  417. @"+", @"+", @"*", @"*", @"#", @"#", nil];
  418. ALL_NORMALIZATION_MAPPINGS = [NSDictionary
  419. dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
  420. @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
  421. // Fullwidth digit 0 to 9
  422. @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3",
  423. @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16",
  424. @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
  425. // Arabic-indic digit 0 to 9
  426. @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3",
  427. @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666",
  428. @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
  429. // Eastern-Arabic digit 0 to 9
  430. @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3",
  431. @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6",
  432. @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9", @"2", @"A",
  433. @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F",
  434. @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J", @"5", @"K",
  435. @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P",
  436. @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T", @"8", @"U",
  437. @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z",
  438. nil];
  439. ALL_PLUS_NUMBER_GROUPING_SYMBOLS = [NSDictionary
  440. dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
  441. @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
  442. @"A", @"A", @"B", @"B", @"C", @"C", @"D", @"D", @"E", @"E",
  443. @"F", @"F", @"G", @"G", @"H", @"H", @"I", @"I", @"J", @"J",
  444. @"K", @"K", @"L", @"L", @"M", @"M", @"N", @"N", @"O", @"O",
  445. @"P", @"P", @"Q", @"Q", @"R", @"R", @"S", @"S", @"T", @"T",
  446. @"U", @"U", @"V", @"V", @"W", @"W", @"X", @"X", @"Y", @"Y",
  447. @"Z", @"Z", @"A", @"a", @"B", @"b", @"C", @"c", @"D", @"d",
  448. @"E", @"e", @"F", @"f", @"G", @"g", @"H", @"h", @"I", @"i",
  449. @"J", @"j", @"K", @"k", @"L", @"l", @"M", @"m", @"N", @"n",
  450. @"O", @"o", @"P", @"p", @"Q", @"q", @"R", @"r", @"S", @"s",
  451. @"T", @"t", @"U", @"u", @"V", @"v", @"W", @"w", @"X", @"x",
  452. @"Y", @"y", @"Z", @"z", @"-", @"-", @"-", @"\uFF0D", @"-",
  453. @"\u2010", @"-", @"\u2011", @"-", @"\u2012", @"-", @"\u2013",
  454. @"-", @"\u2014", @"-", @"\u2015", @"-", @"\u2212", @"/", @"/",
  455. @"/", @"\uFF0F", @" ", @" ", @" ", @"\u3000", @" ", @"\u2060",
  456. @".", @".", @".", @"\uFF0E", nil];
  457. MOBILE_TOKEN_MAPPINGS = @{
  458. @52: @"1",
  459. @54: @"9",
  460. };
  461. });
  462. }
  463. #pragma mark - Metadata manager (phonenumberutil.js) functions -
  464. /**
  465. * Attempts to extract a possible number from the string passed in. This
  466. * currently strips all leading characters that cannot be used to start a phone
  467. * number. Characters that can be used to start a phone number are defined in
  468. * the VALID_START_CHAR_PATTERN. If none of these characters are found in the
  469. * number passed in, an empty string is returned. This function also attempts to
  470. * strip off any alternative extensions or endings if two or more are present,
  471. * such as in the case of: (530) 583-6985 x302/x2303. The second extension here
  472. * makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985
  473. * x2303. We remove the second extension so that the first number is parsed
  474. * correctly.
  475. *
  476. * - param {string} number the string that might contain a phone number.
  477. * @return {string} the number, stripped of any non-phone-number prefix (such as
  478. * 'Tel:') or an empty string if no character used to start phone numbers
  479. * (such as + or any digit) is found in the number.
  480. */
  481. - (NSString *)extractPossibleNumber:(NSString *)number {
  482. number = NormalizeNonBreakingSpace(number);
  483. NSString *possibleNumber = @"";
  484. int start = [self stringPositionByRegex:number regex:VALID_START_CHAR_PATTERN];
  485. if (start >= 0) {
  486. possibleNumber = [number substringFromIndex:start];
  487. // Remove trailing non-alpha non-numerical characters.
  488. possibleNumber =
  489. [self replaceStringByRegex:possibleNumber regex:UNWANTED_END_CHAR_PATTERN withTemplate:@""];
  490. // Check for extra numbers at the end.
  491. int secondNumberStart =
  492. [self stringPositionByRegex:possibleNumber regex:SECOND_NUMBER_START_PATTERN];
  493. if (secondNumberStart > 0) {
  494. possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, secondNumberStart)];
  495. }
  496. } else {
  497. possibleNumber = @"";
  498. }
  499. return possibleNumber;
  500. }
  501. /**
  502. * Checks to see if the string of characters could possibly be a phone number at
  503. * all. At the moment, checks to see that the string begins with at least 2
  504. * digits, ignoring any punctuation commonly found in phone numbers. This method
  505. * does not require the number to be normalized in advance - but does assume
  506. * that leading non-number symbols have been removed, such as by the method
  507. * extractPossibleNumber.
  508. *
  509. * - param {string} number string to be checked for viability as a phone number.
  510. * @return {boolean} NO if the number could be a phone number of some sort,
  511. * otherwise NO.
  512. */
  513. - (BOOL)isViablePhoneNumber:(NSString *)phoneNumber {
  514. phoneNumber = NormalizeNonBreakingSpace(phoneNumber);
  515. if (phoneNumber.length < MIN_LENGTH_FOR_NSN_) {
  516. return NO;
  517. }
  518. return [self matchesEntirely:VALID_PHONE_NUMBER_PATTERN string:phoneNumber];
  519. }
  520. /**
  521. * Normalizes a string of characters representing a phone number. This performs
  522. * the following conversions:
  523. * Punctuation is stripped.
  524. * For ALPHA/VANITY numbers:
  525. * Letters are converted to their numeric representation on a telephone
  526. * keypad. The keypad used here is the one defined in ITU Recommendation
  527. * E.161. This is only done if there are 3 or more letters in the number,
  528. * to lessen the risk that such letters are typos.
  529. * For other numbers:
  530. * Wide-ascii digits are converted to normal ASCII (European) digits.
  531. * Arabic-Indic numerals are converted to European numerals.
  532. * Spurious alpha characters are stripped.
  533. *
  534. * - param {string} number a string of characters representing a phone number.
  535. * @return {string} the normalized string version of the phone number.
  536. */
  537. - (NSString *)normalize:(NSString *)number {
  538. if ([self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:number]) {
  539. return [self normalizeHelper:number
  540. normalizationReplacements:ALL_NORMALIZATION_MAPPINGS
  541. removeNonMatches:true];
  542. } else {
  543. return [self normalizeDigitsOnly:number];
  544. }
  545. return nil;
  546. }
  547. /**
  548. * Normalizes a string of characters representing a phone number. This is a
  549. * wrapper for normalize(String number) but does in-place normalization of the
  550. * StringBuffer provided.
  551. *
  552. * - param {!goog.string.StringBuffer} number a StringBuffer of characters
  553. * representing a phone number that will be normalized in place.
  554. * @private
  555. */
  556. - (void)normalizeSB:(NSString **)number {
  557. if (number == NULL) {
  558. return;
  559. }
  560. (*number) = [self normalize:(*number)];
  561. }
  562. /**
  563. * Normalizes a string of characters representing a phone number. This converts
  564. * wide-ascii and arabic-indic numerals to European numerals, and strips
  565. * punctuation and alpha characters.
  566. *
  567. * - param {string} number a string of characters representing a phone number.
  568. * @return {string} the normalized string version of the phone number.
  569. */
  570. - (NSString *)normalizeDigitsOnly:(NSString *)number {
  571. number = NormalizeNonBreakingSpace(number);
  572. return [self stringByReplacingOccurrencesString:number
  573. withMap:self.DIGIT_MAPPINGS
  574. removeNonMatches:YES];
  575. }
  576. /**
  577. * Normalizes a string of characters representing a phone number. This strips
  578. * all characters which are not diallable on a mobile phone keypad (including
  579. * all non-ASCII digits).
  580. *
  581. * - param {string} number a string of characters representing a phone number.
  582. * @return {string} the normalized string version of the phone number.
  583. */
  584. - (NSString *)normalizeDiallableCharsOnly:(NSString *)number {
  585. number = NormalizeNonBreakingSpace(number);
  586. return [self stringByReplacingOccurrencesString:number
  587. withMap:DIALLABLE_CHAR_MAPPINGS
  588. removeNonMatches:YES];
  589. }
  590. /**
  591. * Converts all alpha characters in a number to their respective digits on a
  592. * keypad, but retains existing formatting. Also converts wide-ascii digits to
  593. * normal ascii digits, and converts Arabic-Indic numerals to European numerals.
  594. *
  595. * - param {string} number a string of characters representing a phone number.
  596. * @return {string} the normalized string version of the phone number.
  597. */
  598. - (NSString *)convertAlphaCharactersInNumber:(NSString *)number {
  599. number = NormalizeNonBreakingSpace(number);
  600. return [self stringByReplacingOccurrencesString:number
  601. withMap:ALL_NORMALIZATION_MAPPINGS
  602. removeNonMatches:NO];
  603. }
  604. /**
  605. * Gets the length of the geographical area code from the
  606. * {@code national_number} field of the PhoneNumber object passed in, so that
  607. * clients could use it to split a national significant number into geographical
  608. * area code and subscriber number. It works in such a way that the resultant
  609. * subscriber number should be diallable, at least on some devices. An example
  610. * of how this could be used:
  611. *
  612. * <pre>
  613. * var phoneUtil = getInstance();
  614. * var number = phoneUtil.parse('16502530000', 'US');
  615. * var nationalSignificantNumber =
  616. * phoneUtil.getNationalSignificantNumber(number);
  617. * var areaCode;
  618. * var subscriberNumber;
  619. *
  620. * var areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
  621. * if (areaCodeLength > 0) {
  622. * areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
  623. * subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
  624. * } else {
  625. * areaCode = '';
  626. * subscriberNumber = nationalSignificantNumber;
  627. * }
  628. * </pre>
  629. *
  630. * N.B.: area code is a very ambiguous concept, so the I18N team generally
  631. * recommends against using it for most purposes, but recommends using the more
  632. * general {@code national_number} instead. Read the following carefully before
  633. * deciding to use this method:
  634. * <ul>
  635. * <li> geographical area codes change over time, and this method honors those
  636. * changes; therefore, it doesn't guarantee the stability of the result it
  637. * produces.
  638. * <li> subscriber numbers may not be diallable from all devices (notably
  639. * mobile devices, which typically requires the full national_number to be
  640. * dialled in most regions).
  641. * <li> most non-geographical numbers have no area codes, including numbers
  642. * from non-geographical entities.
  643. * <li> some geographical numbers have no area codes.
  644. * </ul>
  645. *
  646. * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
  647. * which clients want to know the length of the area code.
  648. * @return {number} the length of area code of the PhoneNumber object passed in.
  649. */
  650. - (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
  651. int res = 0;
  652. @try {
  653. res = [self getLengthOfGeographicalAreaCode:phoneNumber];
  654. } @catch (NSException *exception) {
  655. NSDictionary *userInfo =
  656. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  657. if (error != NULL) {
  658. (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  659. }
  660. }
  661. return res;
  662. }
  663. - (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber {
  664. NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
  665. NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
  666. if (metadata == nil) {
  667. return 0;
  668. }
  669. // If a country doesn't use a national prefix, and this number doesn't have
  670. // an Italian leading zero, we assume it is a closed dialling plan with no
  671. // area codes.
  672. if (metadata.nationalPrefix == nil && phoneNumber.italianLeadingZero == NO) {
  673. return 0;
  674. }
  675. if ([self isNumberGeographical:phoneNumber] == NO) {
  676. return 0;
  677. }
  678. return [self getLengthOfNationalDestinationCode:phoneNumber];
  679. }
  680. /**
  681. * Gets the length of the national destination code (NDC) from the PhoneNumber
  682. * object passed in, so that clients could use it to split a national
  683. * significant number into NDC and subscriber number. The NDC of a phone number
  684. * is normally the first group of digit(s) right after the country calling code
  685. * when the number is formatted in the international format, if there is a
  686. * subscriber number part that follows. An example of how this could be used:
  687. *
  688. * <pre>
  689. * var phoneUtil = getInstance();
  690. * var number = phoneUtil.parse('18002530000', 'US');
  691. * var nationalSignificantNumber =
  692. * phoneUtil.getNationalSignificantNumber(number);
  693. * var nationalDestinationCode;
  694. * var subscriberNumber;
  695. *
  696. * var nationalDestinationCodeLength =
  697. * phoneUtil.getLengthOfNationalDestinationCode(number);
  698. * if (nationalDestinationCodeLength > 0) {
  699. * nationalDestinationCode =
  700. * nationalSignificantNumber.substring(0, nationalDestinationCodeLength);
  701. * subscriberNumber =
  702. * nationalSignificantNumber.substring(nationalDestinationCodeLength);
  703. * } else {
  704. * nationalDestinationCode = '';
  705. * subscriberNumber = nationalSignificantNumber;
  706. * }
  707. * </pre>
  708. *
  709. * Refer to the unittests to see the difference between this function and
  710. * {@link #getLengthOfGeographicalAreaCode}.
  711. *
  712. * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
  713. * which clients want to know the length of the NDC.
  714. * @return {number} the length of NDC of the PhoneNumber object passed in.
  715. */
  716. - (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
  717. int res = 0;
  718. @try {
  719. res = [self getLengthOfNationalDestinationCode:phoneNumber];
  720. } @catch (NSException *exception) {
  721. NSDictionary *userInfo =
  722. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  723. if (error != NULL) {
  724. (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  725. }
  726. }
  727. return res;
  728. }
  729. - (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber {
  730. NBPhoneNumber *copiedProto = nil;
  731. if ([NBMetadataHelper hasValue:phoneNumber.extension]) {
  732. copiedProto = [phoneNumber copy];
  733. copiedProto.extension = nil;
  734. } else {
  735. copiedProto = phoneNumber;
  736. }
  737. NSString *nationalSignificantNumber =
  738. [self format:copiedProto numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  739. NSMutableArray *numberGroups = [[self componentsSeparatedByRegex:nationalSignificantNumber
  740. regex:NON_DIGITS_PATTERN] mutableCopy];
  741. // The pattern will start with '+COUNTRY_CODE ' so the first group will always
  742. // be the empty string (before the + symbol) and the second group will be the
  743. // country calling code. The third group will be area code if it is not the
  744. // last group.
  745. // NOTE: On IE the first group that is supposed to be the empty string does
  746. // not appear in the array of number groups... so make the result on non-IE
  747. // browsers to be that of IE.
  748. if ([numberGroups count] > 0 && ((NSString *)[numberGroups objectAtIndex:0]).length <= 0) {
  749. [numberGroups removeObjectAtIndex:0];
  750. }
  751. if ([numberGroups count] <= 2) {
  752. return 0;
  753. }
  754. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:phoneNumber.countryCode];
  755. BOOL isExists = NO;
  756. for (NSString *regCode in regionCodes) {
  757. if ([regCode isEqualToString:@"AR"]) {
  758. isExists = YES;
  759. break;
  760. }
  761. }
  762. if (isExists && [self getNumberType:phoneNumber] == NBEPhoneNumberTypeMOBILE) {
  763. // Argentinian mobile numbers, when formatted in the international format,
  764. // are in the form of +54 9 NDC XXXX.... As a result, we take the length of
  765. // the third group (NDC) and add 1 for the digit 9, which also forms part of
  766. // the national significant number.
  767. //
  768. // TODO: Investigate the possibility of better modeling the metadata to make
  769. // it easier to obtain the NDC.
  770. return (int)((NSString *)[numberGroups objectAtIndex:2]).length + 1;
  771. }
  772. return (int)((NSString *)[numberGroups objectAtIndex:1]).length;
  773. }
  774. - (NSString *)getCountryMobileTokenFromCountryCode:(NSInteger)countryCallingCode {
  775. NSString *mobileToken = MOBILE_TOKEN_MAPPINGS[@(countryCallingCode)];
  776. if (mobileToken != nil) {
  777. return mobileToken;
  778. }
  779. return @"";
  780. }
  781. /**
  782. * Normalizes a string of characters representing a phone number by replacing
  783. * all characters found in the accompanying map with the values therein, and
  784. * stripping all other characters if removeNonMatches is NO.
  785. *
  786. * - param {string} number a string of characters representing a phone number.
  787. * - param {!Object.<string, string>} normalizationReplacements a mapping of
  788. * characters to what they should be replaced by in the normalized version
  789. * of the phone number.
  790. * - param {boolean} removeNonMatches indicates whether characters that are not
  791. * able to be replaced should be stripped from the number. If this is NO,
  792. * they will be left unchanged in the number.
  793. * @return {string} the normalized string version of the phone number.
  794. * @private
  795. */
  796. - (NSString *)normalizeHelper:(NSString *)sourceString
  797. normalizationReplacements:(NSDictionary *)normalizationReplacements
  798. removeNonMatches:(BOOL)removeNonMatches {
  799. NSUInteger numberLength = sourceString.length;
  800. NSMutableString *normalizedNumber = [[NSMutableString alloc] init];
  801. for (NSUInteger i = 0; i < numberLength; ++i) {
  802. NSString *charString = [sourceString substringWithRange:NSMakeRange(i, 1)];
  803. NSString *newDigit = [normalizationReplacements objectForKey:[charString uppercaseString]];
  804. if (newDigit != nil) {
  805. [normalizedNumber appendString:newDigit];
  806. } else if (removeNonMatches == NO) {
  807. [normalizedNumber appendString:charString];
  808. }
  809. // If neither of the above are NO, we remove this character.
  810. // NSLog(@"[%@]", normalizedNumber);
  811. }
  812. return normalizedNumber;
  813. }
  814. /**
  815. * Helper function to check if the national prefix formatting rule has the first
  816. * group only, i.e., does not start with the national prefix.
  817. *
  818. * - param {string} nationalPrefixFormattingRule The formatting rule for the
  819. * national prefix.
  820. * @return {boolean} NO if the national prefix formatting rule has the first
  821. * group only.
  822. */
  823. - (BOOL)formattingRuleHasFirstGroupOnly:(NSString *)nationalPrefixFormattingRule {
  824. BOOL hasFound = [self stringPositionByRegex:nationalPrefixFormattingRule
  825. regex:FIRST_GROUP_ONLY_PREFIX_PATTERN] >= 0;
  826. return (([nationalPrefixFormattingRule length] == 0) || hasFound);
  827. }
  828. /**
  829. * Tests whether a phone number has a geographical association. It checks if
  830. * the number is associated to a certain region in the country where it belongs
  831. * to. Note that this doesn't verify if the number is actually in use.
  832. *
  833. * - param {i18n.phonenumbers.PhoneNumber} phoneNumber The phone number to test.
  834. * @return {boolean} NO if the phone number has a geographical association.
  835. * @private
  836. */
  837. - (BOOL)isNumberGeographical:(NBPhoneNumber *)phoneNumber {
  838. NBEPhoneNumberType numberType = [self getNumberType:phoneNumber];
  839. // TODO: Include mobile phone numbers from countries like Indonesia, which
  840. // has some mobile numbers that are geographical.
  841. BOOL containGeoMobileContries = [GEO_MOBILE_COUNTRIES containsObject:phoneNumber.countryCode] &&
  842. numberType == NBEPhoneNumberTypeMOBILE;
  843. BOOL isFixedLine = (numberType == NBEPhoneNumberTypeFIXED_LINE);
  844. BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
  845. return isFixedLine || isFixedLineOrMobile || containGeoMobileContries;
  846. }
  847. /**
  848. * Helper function to check region code is not unknown or nil.
  849. *
  850. * - param {?string} regionCode the ISO 3166-1 two-letter region code.
  851. * @return {boolean} NO if region code is valid.
  852. * @private
  853. */
  854. - (BOOL)isValidRegionCode:(NSString *)regionCode {
  855. // In Java we check whether the regionCode is contained in supportedRegions
  856. // that is built out of all the values of countryCallingCodeToRegionCodeMap
  857. // (countryCodeToRegionCodeMap in JS) minus REGION_CODE_FOR_NON_GEO_ENTITY.
  858. // In JS we check whether the regionCode is contained in the keys of
  859. // countryToMetadata but since for non-geographical country calling codes
  860. // (e.g. +800) we use the country calling codes instead of the region code as
  861. // key in the map we have to make sure regionCode is not a number to prevent
  862. // returning NO for non-geographical country calling codes.
  863. return [NBMetadataHelper hasValue:regionCode] && isNan(regionCode) &&
  864. [self.helper getMetadataForRegion:regionCode.uppercaseString] != nil;
  865. }
  866. /**
  867. * Helper function to check the country calling code is valid.
  868. *
  869. * - param {number} countryCallingCode the country calling code.
  870. * @return {boolean} NO if country calling code code is valid.
  871. * @private
  872. */
  873. - (BOOL)hasValidCountryCallingCode:(NSNumber *)countryCallingCode {
  874. id res = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  875. if (res != nil) {
  876. return YES;
  877. }
  878. return NO;
  879. }
  880. /**
  881. * Formats a phone number in the specified format using default rules. Note that
  882. * this does not promise to produce a phone number that the user can dial from
  883. * where they are - although we do format in either 'national' or
  884. * 'international' format depending on what the client asks for, we do not
  885. * currently support a more abbreviated format, such as for users in the same
  886. * 'area' who could potentially dial the number without area code. Note that if
  887. * the phone number has a country calling code of 0 or an otherwise invalid
  888. * country calling code, we cannot work out which formatting rules to apply so
  889. * we return the national significant number with no formatting applied.
  890. *
  891. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  892. * formatted.
  893. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  894. * phone number should be formatted into.
  895. * @return {string} the formatted phone number.
  896. */
  897. - (NSString *)format:(NBPhoneNumber *)phoneNumber
  898. numberFormat:(NBEPhoneNumberFormat)numberFormat
  899. error:(NSError **)error {
  900. NSString *res = nil;
  901. @try {
  902. res = [self format:phoneNumber numberFormat:numberFormat];
  903. } @catch (NSException *exception) {
  904. NSDictionary *userInfo =
  905. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  906. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  907. }
  908. return res;
  909. }
  910. - (NSString *)format:(NBPhoneNumber *)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat {
  911. if ([phoneNumber.nationalNumber isEqualToNumber:@0] &&
  912. [NBMetadataHelper hasValue:phoneNumber.rawInput]) {
  913. // Unparseable numbers that kept their raw input just use that.
  914. // This is the only case where a number can be formatted as E164 without a
  915. // leading '+' symbol (but the original number wasn't parseable anyway).
  916. // TODO: Consider removing the 'if' above so that unparseable strings
  917. // without raw input format to the empty string instead of "+00"
  918. /** @type {string} */
  919. NSString *rawInput = phoneNumber.rawInput;
  920. if ([NBMetadataHelper hasValue:rawInput]) {
  921. return rawInput;
  922. }
  923. }
  924. NSNumber *countryCallingCode = phoneNumber.countryCode;
  925. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];
  926. if (numberFormat == NBEPhoneNumberFormatE164) {
  927. // Early exit for E164 case (even if the country calling code is invalid)
  928. // since no formatting of the national number needs to be applied.
  929. // Extensions are not formatted.
  930. return [self prefixNumberWithCountryCallingCode:countryCallingCode
  931. phoneNumberFormat:NBEPhoneNumberFormatE164
  932. formattedNationalNumber:nationalSignificantNumber
  933. formattedExtension:@""];
  934. }
  935. if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
  936. return nationalSignificantNumber;
  937. }
  938. // Note getRegionCodeForCountryCode() is used because formatting information
  939. // for regions which share a country calling code is contained by only one
  940. // region for performance reasons. For example, for NANPA regions it will be
  941. // contained in the metadata for US.
  942. NSArray *regionCodeArray = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  943. NSString *regionCode = [regionCodeArray objectAtIndex:0];
  944. // Metadata cannot be nil because the country calling code is valid (which
  945. // means that the region code cannot be ZZ and must be one of our supported
  946. // region codes).
  947. NBPhoneMetaData *metadata =
  948. [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  949. NSString *formattedExtension =
  950. [self maybeGetFormattedExtension:phoneNumber metadata:metadata numberFormat:numberFormat];
  951. NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
  952. metadata:metadata
  953. phoneNumberFormat:numberFormat
  954. carrierCode:nil];
  955. return [self prefixNumberWithCountryCallingCode:countryCallingCode
  956. phoneNumberFormat:numberFormat
  957. formattedNationalNumber:formattedNationalNumber
  958. formattedExtension:formattedExtension];
  959. }
  960. /**
  961. * Formats a phone number in the specified format using client-defined
  962. * formatting rules. Note that if the phone number has a country calling code of
  963. * zero or an otherwise invalid country calling code, we cannot work out things
  964. * like whether there should be a national prefix applied, or how to format
  965. * extensions, so we return the national significant number with no formatting
  966. * applied.
  967. *
  968. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  969. * formatted.
  970. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  971. * phone number should be formatted into.
  972. * - param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting
  973. * rules specified by clients.
  974. * @return {string} the formatted phone number.
  975. */
  976. - (NSString *)formatByPattern:(NBPhoneNumber *)number
  977. numberFormat:(NBEPhoneNumberFormat)numberFormat
  978. userDefinedFormats:(NSArray *)userDefinedFormats
  979. error:(NSError **)error {
  980. NSString *res = nil;
  981. @try {
  982. res = [self formatByPattern:number
  983. numberFormat:numberFormat
  984. userDefinedFormats:userDefinedFormats];
  985. } @catch (NSException *exception) {
  986. NSDictionary *userInfo =
  987. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  988. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  989. }
  990. return res;
  991. }
  992. - (NSString *)formatByPattern:(NBPhoneNumber *)number
  993. numberFormat:(NBEPhoneNumberFormat)numberFormat
  994. userDefinedFormats:(NSArray *)userDefinedFormats {
  995. NSNumber *countryCallingCode = number.countryCode;
  996. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  997. if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
  998. return nationalSignificantNumber;
  999. }
  1000. // Note getRegionCodeForCountryCode() is used because formatting information
  1001. // for regions which share a country calling code is contained by only one
  1002. // region for performance reasons. For example, for NANPA regions it will be
  1003. // contained in the metadata for US.
  1004. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  1005. NSString *regionCode = nil;
  1006. if (regionCodes != nil && regionCodes.count > 0) {
  1007. regionCode = [regionCodes objectAtIndex:0];
  1008. }
  1009. // Metadata cannot be nil because the country calling code is valid
  1010. /** @type {i18n.phonenumbers.PhoneMetadata} */
  1011. NBPhoneMetaData *metadata =
  1012. [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  1013. NSString *formattedNumber = @"";
  1014. NBNumberFormat *formattingPattern =
  1015. [self chooseFormattingPatternForNumber:userDefinedFormats
  1016. nationalNumber:nationalSignificantNumber];
  1017. if (formattingPattern == nil) {
  1018. // If no pattern above is matched, we format the number as a whole.
  1019. formattedNumber = nationalSignificantNumber;
  1020. } else {
  1021. // Before we do a replacement of the national prefix pattern $NP with the
  1022. // national prefix, we need to copy the rule so that subsequent replacements
  1023. // for different numbers have the appropriate national prefix.
  1024. NBNumberFormat *numFormatCopy = [formattingPattern copy];
  1025. NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;
  1026. if (nationalPrefixFormattingRule.length > 0) {
  1027. NSString *nationalPrefix = metadata.nationalPrefix;
  1028. if (nationalPrefix.length > 0) {
  1029. // Replace $NP with national prefix and $FG with the first group ($1).
  1030. nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule
  1031. regex:NP_PATTERN
  1032. withTemplate:nationalPrefix];
  1033. nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule
  1034. regex:FG_PATTERN
  1035. withTemplate:@"\\$1"];
  1036. numFormatCopy.nationalPrefixFormattingRule = nationalPrefixFormattingRule;
  1037. } else {
  1038. // We don't want to have a rule for how to format the national prefix if
  1039. // there isn't one.
  1040. numFormatCopy.nationalPrefixFormattingRule = @"";
  1041. }
  1042. }
  1043. formattedNumber = [self formatNsnUsingPattern:nationalSignificantNumber
  1044. formattingPattern:numFormatCopy
  1045. numberFormat:numberFormat
  1046. carrierCode:nil];
  1047. }
  1048. NSString *formattedExtension =
  1049. [self maybeGetFormattedExtension:number metadata:metadata numberFormat:numberFormat];
  1050. // NSLog(@"!@# prefixNumberWithCountryCallingCode called [%@]", formattedExtension);
  1051. return [self prefixNumberWithCountryCallingCode:countryCallingCode
  1052. phoneNumberFormat:numberFormat
  1053. formattedNationalNumber:formattedNumber
  1054. formattedExtension:formattedExtension];
  1055. }
  1056. /**
  1057. * Formats a phone number in national format for dialing using the carrier as
  1058. * specified in the {@code carrierCode}. The {@code carrierCode} will always be
  1059. * used regardless of whether the phone number already has a preferred domestic
  1060. * carrier code stored. If {@code carrierCode} contains an empty string, returns
  1061. * the number in national format without any carrier code.
  1062. *
  1063. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  1064. * formatted.
  1065. * - param {string} carrierCode the carrier selection code to be used.
  1066. * @return {string} the formatted phone number in national format for dialing
  1067. * using the carrier as specified in the {@code carrierCode}.
  1068. */
  1069. - (NSString *)formatNationalNumberWithCarrierCode:(NBPhoneNumber *)number
  1070. carrierCode:(NSString *)carrierCode
  1071. error:(NSError **)error {
  1072. NSString *res = nil;
  1073. @try {
  1074. res = [self formatNationalNumberWithCarrierCode:number carrierCode:carrierCode];
  1075. } @catch (NSException *exception) {
  1076. NSDictionary *userInfo =
  1077. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1078. if (error != NULL) {
  1079. (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1080. }
  1081. }
  1082. return res;
  1083. }
  1084. - (NSString *)formatNationalNumberWithCarrierCode:(NBPhoneNumber *)number
  1085. carrierCode:(NSString *)carrierCode {
  1086. NSNumber *countryCallingCode = number.countryCode;
  1087. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  1088. if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
  1089. return nationalSignificantNumber;
  1090. }
  1091. // Note getRegionCodeForCountryCode() is used because formatting information
  1092. // for regions which share a country calling code is contained by only one
  1093. // region for performance reasons. For example, for NANPA regions it will be
  1094. // contained in the metadata for US.
  1095. NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  1096. // Metadata cannot be nil because the country calling code is valid.
  1097. NBPhoneMetaData *metadata =
  1098. [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  1099. NSString *formattedExtension = [self maybeGetFormattedExtension:number
  1100. metadata:metadata
  1101. numberFormat:NBEPhoneNumberFormatNATIONAL];
  1102. NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
  1103. metadata:metadata
  1104. phoneNumberFormat:NBEPhoneNumberFormatNATIONAL
  1105. carrierCode:carrierCode];
  1106. return [self prefixNumberWithCountryCallingCode:countryCallingCode
  1107. phoneNumberFormat:NBEPhoneNumberFormatNATIONAL
  1108. formattedNationalNumber:formattedNationalNumber
  1109. formattedExtension:formattedExtension];
  1110. }
  1111. /**
  1112. * - param {number} countryCallingCode
  1113. * - param {?string} regionCode
  1114. * @return {i18n.phonenumbers.PhoneMetadata}
  1115. * @private
  1116. */
  1117. - (NBPhoneMetaData *)getMetadataForRegionOrCallingCode:(NSNumber *)countryCallingCode
  1118. regionCode:(NSString *)regionCode {
  1119. NBMetadataHelper *helper = self.helper;
  1120. return [regionCode isEqualToString:NB_REGION_CODE_FOR_NON_GEO_ENTITY]
  1121. ? [helper getMetadataForNonGeographicalRegion:countryCallingCode]
  1122. : [helper getMetadataForRegion:regionCode];
  1123. }
  1124. /**
  1125. * Formats a phone number in national format for dialing using the carrier as
  1126. * specified in the preferred_domestic_carrier_code field of the PhoneNumber
  1127. * object passed in. If that is missing, use the {@code fallbackCarrierCode}
  1128. * passed in instead. If there is no {@code preferred_domestic_carrier_code},
  1129. * and the {@code fallbackCarrierCode} contains an empty string, return the
  1130. * number in national format without any carrier code.
  1131. *
  1132. * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier
  1133. * code passed in should take precedence over the number's
  1134. * {@code preferred_domestic_carrier_code} when formatting.
  1135. *
  1136. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  1137. * formatted.
  1138. * - param {string} fallbackCarrierCode the carrier selection code to be used, if
  1139. * none is found in the phone number itself.
  1140. * @return {string} the formatted phone number in national format for dialing
  1141. * using the number's preferred_domestic_carrier_code, or the
  1142. * {@code fallbackCarrierCode} passed in if none is found.
  1143. */
  1144. - (NSString *)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber *)number
  1145. fallbackCarrierCode:(NSString *)fallbackCarrierCode
  1146. error:(NSError **)error {
  1147. NSString *res = nil;
  1148. @try {
  1149. res = [self formatNationalNumberWithCarrierCode:number carrierCode:fallbackCarrierCode];
  1150. } @catch (NSException *exception) {
  1151. NSDictionary *userInfo =
  1152. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1153. if (error != NULL) {
  1154. (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1155. }
  1156. }
  1157. return res;
  1158. }
  1159. - (NSString *)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber *)number
  1160. fallbackCarrierCode:(NSString *)fallbackCarrierCode {
  1161. NSString *domesticCarrierCode = number.preferredDomesticCarrierCode != nil
  1162. ? number.preferredDomesticCarrierCode
  1163. : fallbackCarrierCode;
  1164. return [self formatNationalNumberWithCarrierCode:number carrierCode:domesticCarrierCode];
  1165. }
  1166. /**
  1167. * Returns a number formatted in such a way that it can be dialed from a mobile
  1168. * phone in a specific region. If the number cannot be reached from the region
  1169. * (e.g. some countries block toll-free numbers from being called outside of the
  1170. * country), the method returns an empty string.
  1171. *
  1172. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  1173. * formatted.
  1174. * - param {string} regionCallingFrom the region where the call is being placed.
  1175. * - param {boolean} withFormatting whether the number should be returned with
  1176. * formatting symbols, such as spaces and dashes.
  1177. * @return {string} the formatted phone number.
  1178. */
  1179. - (NSString *)formatNumberForMobileDialing:(NBPhoneNumber *)number
  1180. regionCallingFrom:(NSString *)regionCallingFrom
  1181. withFormatting:(BOOL)withFormatting
  1182. error:(NSError **)error {
  1183. NSString *res = nil;
  1184. @try {
  1185. res = [self formatNumberForMobileDialing:number
  1186. regionCallingFrom:regionCallingFrom
  1187. withFormatting:withFormatting];
  1188. } @catch (NSException *exception) {
  1189. NSDictionary *userInfo =
  1190. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1191. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1192. }
  1193. return res;
  1194. }
  1195. - (NSString *)formatNumberForMobileDialing:(NBPhoneNumber *)number
  1196. regionCallingFrom:(NSString *)regionCallingFrom
  1197. withFormatting:(BOOL)withFormatting {
  1198. NSNumber *countryCallingCode = number.countryCode;
  1199. if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
  1200. return [NBMetadataHelper hasValue:number.rawInput] ? number.rawInput : @"";
  1201. }
  1202. NSString *formattedNumber = @"";
  1203. // Clear the extension, as that part cannot normally be dialed together with
  1204. // the main number.
  1205. NBPhoneNumber *numberNoExt = [number copy];
  1206. numberNoExt.extension = @"";
  1207. NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  1208. if ([regionCallingFrom isEqualToString:regionCode]) {
  1209. NBEPhoneNumberType numberType = [self getNumberType:numberNoExt];
  1210. BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE) ||
  1211. (numberType == NBEPhoneNumberTypeMOBILE) ||
  1212. (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
  1213. // Carrier codes may be needed in some countries. We handle this here.
  1214. if ([regionCode isEqualToString:@"CO"] && numberType == NBEPhoneNumberTypeFIXED_LINE) {
  1215. formattedNumber =
  1216. [self formatNationalNumberWithCarrierCode:numberNoExt
  1217. carrierCode:COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX];
  1218. } else if ([regionCode isEqualToString:@"BR"] && isFixedLineOrMobile) {
  1219. formattedNumber = [NBMetadataHelper hasValue:numberNoExt.preferredDomesticCarrierCode]
  1220. ? [self formatNationalNumberWithPreferredCarrierCode:numberNoExt
  1221. fallbackCarrierCode:@""]
  1222. : @"";
  1223. // Brazilian fixed line and mobile numbers need to be dialed with a
  1224. // carrier code when called within Brazil. Without that, most of the
  1225. // carriers won't connect the call. Because of that, we return an
  1226. // empty string here.
  1227. } else {
  1228. // For NANPA countries, non-geographical countries, and Mexican fixed
  1229. // line and mobile numbers, we output international format for numbersi
  1230. // that can be dialed internationally as that always works.
  1231. if ((countryCallingCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_ ||
  1232. [regionCode isEqualToString:NB_REGION_CODE_FOR_NON_GEO_ENTITY] ||
  1233. // MX fixed line and mobile numbers should always be formatted in
  1234. // international format, even when dialed within MX. For national
  1235. // format to work, a carrier code needs to be used, and the correct
  1236. // carrier code depends on if the caller and callee are from the
  1237. // same local area. It is trickier to get that to work correctly than
  1238. // using international format, which is tested to work fine on all
  1239. // carriers.
  1240. ([regionCode isEqualToString:@"MX"] && isFixedLineOrMobile)) &&
  1241. [self canBeInternationallyDialled:numberNoExt]) {
  1242. formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  1243. } else {
  1244. formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatNATIONAL];
  1245. }
  1246. }
  1247. } else if ([self canBeInternationallyDialled:numberNoExt]) {
  1248. return withFormatting ? [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL]
  1249. : [self format:numberNoExt numberFormat:NBEPhoneNumberFormatE164];
  1250. }
  1251. return withFormatting ? formattedNumber
  1252. : [self normalizeHelper:formattedNumber
  1253. normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
  1254. removeNonMatches:YES];
  1255. }
  1256. /**
  1257. * Formats a phone number for out-of-country dialing purposes. If no
  1258. * regionCallingFrom is supplied, we format the number in its INTERNATIONAL
  1259. * format. If the country calling code is the same as that of the region where
  1260. * the number is from, then NATIONAL formatting will be applied.
  1261. *
  1262. * <p>If the number itself has a country calling code of zero or an otherwise
  1263. * invalid country calling code, then we return the number with no formatting
  1264. * applied.
  1265. *
  1266. * <p>Note this function takes care of the case for calling inside of NANPA and
  1267. * between Russia and Kazakhstan (who share the same country calling code). In
  1268. * those cases, no international prefix is used. For regions which have multiple
  1269. * international prefixes, the number in its INTERNATIONAL format will be
  1270. * returned instead.
  1271. *
  1272. * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
  1273. * formatted.
  1274. * - param {string} regionCallingFrom the region where the call is being placed.
  1275. * @return {string} the formatted phone number.
  1276. */
  1277. - (NSString *)formatOutOfCountryCallingNumber:(NBPhoneNumber *)number
  1278. regionCallingFrom:(NSString *)regionCallingFrom
  1279. error:(NSError **)error {
  1280. NSString *res = nil;
  1281. @try {
  1282. res = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
  1283. } @catch (NSException *exception) {
  1284. NSDictionary *userInfo =
  1285. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1286. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1287. }
  1288. return res;
  1289. }
  1290. - (NSString *)formatOutOfCountryCallingNumber:(NBPhoneNumber *)number
  1291. regionCallingFrom:(NSString *)regionCallingFrom {
  1292. if ([self isValidRegionCode:regionCallingFrom] == NO) {
  1293. return [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  1294. }
  1295. NSNumber *countryCallingCode = [number.countryCode copy];
  1296. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  1297. if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
  1298. return nationalSignificantNumber;
  1299. }
  1300. if (countryCallingCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_) {
  1301. if ([self isNANPACountry:regionCallingFrom]) {
  1302. // For NANPA regions, return the national format for these regions but
  1303. // prefix it with the country calling code.
  1304. return [NSString
  1305. stringWithFormat:@"%@ %@", countryCallingCode,
  1306. [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]];
  1307. }
  1308. } else if ([countryCallingCode
  1309. isEqualToNumber:[self getCountryCodeForValidRegion:regionCallingFrom error:nil]]) {
  1310. // If regions share a country calling code, the country calling code need
  1311. // not be dialled. This also applies when dialling within a region, so this
  1312. // if clause covers both these cases. Technically this is the case for
  1313. // dialling from La Reunion to other overseas departments of France (French
  1314. // Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover
  1315. // this edge case for now and for those cases return the version including
  1316. // country calling code. Details here:
  1317. // http://www.petitfute.com/voyage/225-info-pratiques-reunion
  1318. return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
  1319. }
  1320. // Metadata cannot be nil because we checked 'isValidRegionCode()' above.
  1321. NBPhoneMetaData *metadataForRegionCallingFrom =
  1322. [self.helper getMetadataForRegion:regionCallingFrom];
  1323. NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;
  1324. // For regions that have multiple international prefixes, the international
  1325. // format of the number is returned, unless there is a preferred international
  1326. // prefix.
  1327. NSString *internationalPrefixForFormatting = @"";
  1328. if ([self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX string:internationalPrefix]) {
  1329. internationalPrefixForFormatting = internationalPrefix;
  1330. } else if ([NBMetadataHelper
  1331. hasValue:metadataForRegionCallingFrom.preferredInternationalPrefix]) {
  1332. internationalPrefixForFormatting = metadataForRegionCallingFrom.preferredInternationalPrefix;
  1333. }
  1334. NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  1335. // Metadata cannot be nil because the country calling code is valid.
  1336. NBPhoneMetaData *metadataForRegion =
  1337. [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  1338. NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
  1339. metadata:metadataForRegion
  1340. phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
  1341. carrierCode:nil];
  1342. NSString *formattedExtension =
  1343. [self maybeGetFormattedExtension:number
  1344. metadata:metadataForRegion
  1345. numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  1346. NSString *hasLenth =
  1347. [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting,
  1348. countryCallingCode, formattedNationalNumber, formattedExtension];
  1349. NSString *hasNotLength =
  1350. [self prefixNumberWithCountryCallingCode:countryCallingCode
  1351. phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
  1352. formattedNationalNumber:formattedNationalNumber
  1353. formattedExtension:formattedExtension];
  1354. return internationalPrefixForFormatting.length > 0 ? hasLenth : hasNotLength;
  1355. }
  1356. /**
  1357. * A helper function that is used by format and formatByPattern.
  1358. *
  1359. * - param {number} countryCallingCode the country calling code.
  1360. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  1361. * phone number should be formatted into.
  1362. * - param {string} formattedNationalNumber
  1363. * - param {string} formattedExtension
  1364. * @return {string} the formatted phone number.
  1365. * @private
  1366. */
  1367. - (NSString *)prefixNumberWithCountryCallingCode:(NSNumber *)countryCallingCode
  1368. phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat
  1369. formattedNationalNumber:(NSString *)formattedNationalNumber
  1370. formattedExtension:(NSString *)formattedExtension {
  1371. switch (numberFormat) {
  1372. case NBEPhoneNumberFormatE164:
  1373. return [NSString stringWithFormat:@"+%@%@%@", countryCallingCode, formattedNationalNumber,
  1374. formattedExtension];
  1375. case NBEPhoneNumberFormatINTERNATIONAL:
  1376. return [NSString stringWithFormat:@"+%@ %@%@", countryCallingCode, formattedNationalNumber,
  1377. formattedExtension];
  1378. case NBEPhoneNumberFormatRFC3966:
  1379. return [NSString stringWithFormat:@"%@+%@-%@%@", RFC3966_PREFIX, countryCallingCode,
  1380. formattedNationalNumber, formattedExtension];
  1381. case NBEPhoneNumberFormatNATIONAL:
  1382. default:
  1383. return [NSString stringWithFormat:@"%@%@", formattedNationalNumber, formattedExtension];
  1384. }
  1385. }
  1386. /**
  1387. * Formats a phone number using the original phone number format that the number
  1388. * is parsed from. The original format is embedded in the country_code_source
  1389. * field of the PhoneNumber object passed in. If such information is missing,
  1390. * the number will be formatted into the NATIONAL format by default. When the
  1391. * number contains a leading zero and this is unexpected for this country, or we
  1392. * don't have a formatting pattern for the number, the method returns the raw
  1393. * input when it is available.
  1394. *
  1395. * Note this method guarantees no digit will be inserted, removed or modified as
  1396. * a result of formatting.
  1397. *
  1398. * - param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
  1399. * be formatted in its original number format.
  1400. * - param {string} regionCallingFrom the region whose IDD needs to be prefixed
  1401. * if the original number has one.
  1402. * @return {string} the formatted phone number in its original number format.
  1403. */
  1404. - (NSString *)formatInOriginalFormat:(NBPhoneNumber *)number
  1405. regionCallingFrom:(NSString *)regionCallingFrom
  1406. error:(NSError **)error {
  1407. NSString *res = nil;
  1408. @try {
  1409. res = [self formatInOriginalFormat:number regionCallingFrom:regionCallingFrom];
  1410. } @catch (NSException *exception) {
  1411. NSDictionary *userInfo =
  1412. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1413. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1414. }
  1415. return res;
  1416. }
  1417. - (NSString *)formatInOriginalFormat:(NBPhoneNumber *)number
  1418. regionCallingFrom:(NSString *)regionCallingFrom {
  1419. if ([NBMetadataHelper hasValue:number.rawInput] &&
  1420. ([self hasFormattingPatternForNumber:number] == NO)) {
  1421. // We check if we have the formatting pattern because without that, we might
  1422. // format the number as a group without national prefix.
  1423. return number.rawInput;
  1424. }
  1425. if (number.countryCodeSource == nil) {
  1426. return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
  1427. }
  1428. NSString *formattedNumber = @"";
  1429. switch ([number.countryCodeSource integerValue]) {
  1430. case NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN:
  1431. formattedNumber = [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  1432. break;
  1433. case NBECountryCodeSourceFROM_NUMBER_WITH_IDD:
  1434. formattedNumber =
  1435. [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
  1436. break;
  1437. case NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN:
  1438. formattedNumber = [[self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL]
  1439. substringFromIndex:1];
  1440. break;
  1441. case NBECountryCodeSourceFROM_DEFAULT_COUNTRY:
  1442. // Fall-through to default case.
  1443. default: {
  1444. NSString *regionCode = [self getRegionCodeForCountryCode:number.countryCode];
  1445. // We strip non-digits from the NDD here, and from the raw input later,
  1446. // so that we can compare them easily.
  1447. NSString *nationalPrefix = [self getNddPrefixForRegion:regionCode stripNonDigits:YES];
  1448. NSString *nationalFormat = [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
  1449. if (nationalPrefix == nil || nationalPrefix.length == 0) {
  1450. // If the region doesn't have a national prefix at all, we can safely
  1451. // return the national format without worrying about a national prefix
  1452. // being added.
  1453. formattedNumber = nationalFormat;
  1454. break;
  1455. }
  1456. // Otherwise, we check if the original number was entered with a national
  1457. // prefix.
  1458. if ([self rawInputContainsNationalPrefix:number.rawInput
  1459. nationalPrefix:nationalPrefix
  1460. regionCode:regionCode]) {
  1461. // If so, we can safely return the national format.
  1462. formattedNumber = nationalFormat;
  1463. break;
  1464. }
  1465. // Metadata cannot be nil here because getNddPrefixForRegion() (above)
  1466. // returns nil if there is no metadata for the region.
  1467. NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
  1468. NSString *nationalNumber = [self getNationalSignificantNumber:number];
  1469. NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats
  1470. nationalNumber:nationalNumber];
  1471. // The format rule could still be nil here if the national number was 0
  1472. // and there was no raw input (this should not be possible for numbers
  1473. // generated by the phonenumber library as they would also not have a
  1474. // country calling code and we would have exited earlier).
  1475. if (formatRule == nil) {
  1476. formattedNumber = nationalFormat;
  1477. break;
  1478. }
  1479. // When the format we apply to this number doesn't contain national
  1480. // prefix, we can just return the national format.
  1481. // TODO: Refactor the code below with the code in
  1482. // isNationalPrefixPresentIfRequired.
  1483. NSString *candidateNationalPrefixRule = formatRule.nationalPrefixFormattingRule;
  1484. // We assume that the first-group symbol will never be _before_ the
  1485. // national prefix.
  1486. NSRange firstGroupRange = [candidateNationalPrefixRule rangeOfString:@"$1"];
  1487. if (firstGroupRange.location == NSNotFound) {
  1488. formattedNumber = nationalFormat;
  1489. break;
  1490. }
  1491. if (firstGroupRange.location <= 0) {
  1492. formattedNumber = nationalFormat;
  1493. break;
  1494. }
  1495. candidateNationalPrefixRule =
  1496. [candidateNationalPrefixRule substringWithRange:NSMakeRange(0, firstGroupRange.location)];
  1497. candidateNationalPrefixRule = [self normalizeDigitsOnly:candidateNationalPrefixRule];
  1498. if (candidateNationalPrefixRule.length == 0) {
  1499. // National prefix not used when formatting this number.
  1500. formattedNumber = nationalFormat;
  1501. break;
  1502. }
  1503. // Otherwise, we need to remove the national prefix from our output.
  1504. NBNumberFormat *numFormatCopy = [formatRule copy];
  1505. numFormatCopy.nationalPrefixFormattingRule = nil;
  1506. formattedNumber = [self formatByPattern:number
  1507. numberFormat:NBEPhoneNumberFormatNATIONAL
  1508. userDefinedFormats:@[ numFormatCopy ]];
  1509. break;
  1510. }
  1511. }
  1512. NSString *rawInput = number.rawInput;
  1513. // If no digit is inserted/removed/modified as a result of our formatting, we
  1514. // return the formatted phone number; otherwise we return the raw input the
  1515. // user entered.
  1516. if (formattedNumber != nil && rawInput.length > 0) {
  1517. NSString *normalizedFormattedNumber = [self normalizeHelper:formattedNumber
  1518. normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
  1519. removeNonMatches:YES];
  1520. /** @type {string} */
  1521. NSString *normalizedRawInput = [self normalizeHelper:rawInput
  1522. normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
  1523. removeNonMatches:YES];
  1524. if ([normalizedFormattedNumber isEqualToString:normalizedRawInput] == NO) {
  1525. formattedNumber = rawInput;
  1526. }
  1527. }
  1528. return formattedNumber;
  1529. }
  1530. /**
  1531. * Check if rawInput, which is assumed to be in the national format, has a
  1532. * national prefix. The national prefix is assumed to be in digits-only form.
  1533. * - param {string} rawInput
  1534. * - param {string} nationalPrefix
  1535. * - param {string} regionCode
  1536. * @return {boolean}
  1537. * @private
  1538. */
  1539. - (BOOL)rawInputContainsNationalPrefix:(NSString *)rawInput
  1540. nationalPrefix:(NSString *)nationalPrefix
  1541. regionCode:(NSString *)regionCode {
  1542. BOOL isValid = NO;
  1543. NSString *normalizedNationalNumber = [self normalizeDigitsOnly:rawInput];
  1544. if ([self isStartingStringByRegex:normalizedNationalNumber regex:nationalPrefix]) {
  1545. // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the
  1546. // national prefix when written without it (e.g. 0777123) if we just do
  1547. // prefix matching. To tackle that, we check the validity of the number if
  1548. // the assumed national prefix is removed (777123 won't be valid in
  1549. // Japan).
  1550. NSString *subString = [normalizedNationalNumber substringFromIndex:nationalPrefix.length];
  1551. NSError *anError = nil;
  1552. isValid = [self isValidNumber:[self parse:subString defaultRegion:regionCode error:&anError]];
  1553. if (anError != nil) return NO;
  1554. }
  1555. return isValid;
  1556. }
  1557. /**
  1558. * Returns NO if a number is from a region whose national significant number
  1559. * couldn't contain a leading zero, but has the italian_leading_zero field set
  1560. * to NO.
  1561. * - param {i18n.phonenumbers.PhoneNumber} number
  1562. * @return {boolean}
  1563. * @private
  1564. */
  1565. - (BOOL)hasUnexpectedItalianLeadingZero:(NBPhoneNumber *)number {
  1566. return number.italianLeadingZero && [self isLeadingZeroPossible:number.countryCode] == NO;
  1567. }
  1568. /**
  1569. * - param {i18n.phonenumbers.PhoneNumber} number
  1570. * @return {boolean}
  1571. * @private
  1572. */
  1573. - (BOOL)hasFormattingPatternForNumber:(NBPhoneNumber *)number {
  1574. NSNumber *countryCallingCode = number.countryCode;
  1575. NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCallingCode];
  1576. NBPhoneMetaData *metadata =
  1577. [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:phoneNumberRegion];
  1578. if (metadata == nil) {
  1579. return NO;
  1580. }
  1581. NSString *nationalNumber = [self getNationalSignificantNumber:number];
  1582. NBNumberFormat *formatRule =
  1583. [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber];
  1584. return formatRule != nil;
  1585. }
  1586. /**
  1587. * Formats a phone number for out-of-country dialing purposes.
  1588. *
  1589. * Note that in this version, if the number was entered originally using alpha
  1590. * characters and this version of the number is stored in raw_input, this
  1591. * representation of the number will be used rather than the digit
  1592. * representation. Grouping information, as specified by characters such as '-'
  1593. * and ' ', will be retained.
  1594. *
  1595. * <p><b>Caveats:</b></p>
  1596. * <ul>
  1597. * <li>This will not produce good results if the country calling code is both
  1598. * present in the raw input _and_ is the start of the national number. This is
  1599. * not a problem in the regions which typically use alpha numbers.
  1600. * <li>This will also not produce good results if the raw input has any grouping
  1601. * information within the first three digits of the national number, and if the
  1602. * function needs to strip preceding digits/words in the raw input before these
  1603. * digits. Normally people group the first three digits together so this is not
  1604. * a huge problem - and will be fixed if it proves to be so.
  1605. * </ul>
  1606. *
  1607. * - param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
  1608. * be formatted.
  1609. * - param {string} regionCallingFrom the region where the call is being placed.
  1610. * @return {string} the formatted phone number.
  1611. */
  1612. - (NSString *)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber *)number
  1613. regionCallingFrom:(NSString *)regionCallingFrom
  1614. error:(NSError **)error {
  1615. NSString *res = nil;
  1616. @try {
  1617. res = [self formatOutOfCountryKeepingAlphaChars:number regionCallingFrom:regionCallingFrom];
  1618. } @catch (NSException *exception) {
  1619. NSDictionary *userInfo =
  1620. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  1621. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  1622. }
  1623. return res;
  1624. }
  1625. - (NSString *)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber *)number
  1626. regionCallingFrom:(NSString *)regionCallingFrom {
  1627. NSString *rawInput = number.rawInput;
  1628. // If there is no raw input, then we can't keep alpha characters because there
  1629. // aren't any. In this case, we return formatOutOfCountryCallingNumber.
  1630. if (rawInput == nil || rawInput.length == 0) {
  1631. return [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
  1632. }
  1633. NSNumber *countryCode = number.countryCode;
  1634. if ([self hasValidCountryCallingCode:countryCode] == NO) {
  1635. return rawInput;
  1636. }
  1637. // Strip any prefix such as country calling code, IDD, that was present. We do
  1638. // this by comparing the number in raw_input with the parsed number. To do
  1639. // this, first we normalize punctuation. We retain number grouping symbols
  1640. // such as ' ' only.
  1641. rawInput = [self normalizeHelper:rawInput
  1642. normalizationReplacements:ALL_PLUS_NUMBER_GROUPING_SYMBOLS
  1643. removeNonMatches:NO];
  1644. // NSLog(@"---- formatOutOfCountryKeepingAlphaChars normalizeHelper rawInput [%@]", rawInput);
  1645. // Now we trim everything before the first three digits in the parsed number.
  1646. // We choose three because all valid alpha numbers have 3 digits at the start
  1647. // - if it does not, then we don't trim anything at all. Similarly, if the
  1648. // national number was less than three digits, we don't trim anything at all.
  1649. NSString *nationalNumber = [self getNationalSignificantNumber:number];
  1650. if (nationalNumber.length > 3) {
  1651. int firstNationalNumberDigit =
  1652. [self indexOfStringByString:rawInput
  1653. target:[nationalNumber substringWithRange:NSMakeRange(0, 3)]];
  1654. if (firstNationalNumberDigit != -1) {
  1655. rawInput = [rawInput substringFromIndex:firstNationalNumberDigit];
  1656. }
  1657. }
  1658. NBPhoneMetaData *metadataForRegionCallingFrom =
  1659. [self.helper getMetadataForRegion:regionCallingFrom];
  1660. if (countryCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_) {
  1661. if ([self isNANPACountry:regionCallingFrom]) {
  1662. return [NSString stringWithFormat:@"%@ %@", countryCode, rawInput];
  1663. }
  1664. } else if (metadataForRegionCallingFrom != nil &&
  1665. [countryCode
  1666. isEqualToNumber:[self getCountryCodeForValidRegion:regionCallingFrom error:nil]]) {
  1667. NBNumberFormat *formattingPattern =
  1668. [self chooseFormattingPatternForNumber:metadataForRegionCallingFrom.numberFormats
  1669. nationalNumber:nationalNumber];
  1670. if (formattingPattern == nil) {
  1671. // If no pattern above is matched, we format the original input.
  1672. return rawInput;
  1673. }
  1674. NBNumberFormat *newFormat = [formattingPattern copy];
  1675. // The first group is the first group of digits that the user wrote
  1676. // together.
  1677. newFormat.pattern = @"(\\d+)(.*)";
  1678. // Here we just concatenate them back together after the national prefix
  1679. // has been fixed.
  1680. newFormat.format = @"$1$2";
  1681. // Now we format using this pattern instead of the default pattern, but
  1682. // with the national prefix prefixed if necessary.
  1683. // This will not work in the cases where the pattern (and not the leading
  1684. // digits) decide whether a national prefix needs to be used, since we have
  1685. // overridden the pattern to match anything, but that is not the case in the
  1686. // metadata to date.
  1687. return [self formatNsnUsingPattern:rawInput
  1688. formattingPattern:newFormat
  1689. numberFormat:NBEPhoneNumberFormatNATIONAL
  1690. carrierCode:nil];
  1691. }
  1692. NSString *internationalPrefixForFormatting = @"";
  1693. // If an unsupported region-calling-from is entered, or a country with
  1694. // multiple international prefixes, the international format of the number is
  1695. // returned, unless there is a preferred international prefix.
  1696. if (metadataForRegionCallingFrom != nil) {
  1697. NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;
  1698. internationalPrefixForFormatting =
  1699. [self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX string:internationalPrefix]
  1700. ? internationalPrefix
  1701. : metadataForRegionCallingFrom.preferredInternationalPrefix;
  1702. }
  1703. NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
  1704. // Metadata cannot be nil because the country calling code is valid.
  1705. NBPhoneMetaData *metadataForRegion =
  1706. [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  1707. NSString *formattedExtension =
  1708. [self maybeGetFormattedExtension:number
  1709. metadata:metadataForRegion
  1710. numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  1711. if (internationalPrefixForFormatting.length > 0) {
  1712. return [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCode,
  1713. rawInput, formattedExtension];
  1714. } else {
  1715. // Invalid region entered as country-calling-from (so no metadata was found
  1716. // for it) or the region chosen has multiple international dialling
  1717. // prefixes.
  1718. return [self prefixNumberWithCountryCallingCode:countryCode
  1719. phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
  1720. formattedNationalNumber:rawInput
  1721. formattedExtension:formattedExtension];
  1722. }
  1723. }
  1724. /**
  1725. * Note in some regions, the national number can be written in two completely
  1726. * different ways depending on whether it forms part of the NATIONAL format or
  1727. * INTERNATIONAL format. The numberFormat parameter here is used to specify
  1728. * which format to use for those cases. If a carrierCode is specified, this will
  1729. * be inserted into the formatted string to replace $CC.
  1730. *
  1731. * - param {string} number a string of characters representing a phone number.
  1732. * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
  1733. * region that we think this number is from.
  1734. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  1735. * phone number should be formatted into.
  1736. * - param {string=} opt_carrierCode
  1737. * @return {string} the formatted phone number.
  1738. * @private
  1739. */
  1740. - (NSString *)formatNsn:(NSString *)phoneNumber
  1741. metadata:(NBPhoneMetaData *)metadata
  1742. phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat
  1743. carrierCode:(NSString *)opt_carrierCode {
  1744. NSArray *intlNumberFormats = metadata.intlNumberFormats;
  1745. // When the intlNumberFormats exists, we use that to format national number
  1746. // for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
  1747. NSArray *availableFormats =
  1748. ([intlNumberFormats count] <= 0 || numberFormat == NBEPhoneNumberFormatNATIONAL)
  1749. ? metadata.numberFormats
  1750. : intlNumberFormats;
  1751. NBNumberFormat *formattingPattern =
  1752. [self chooseFormattingPatternForNumber:availableFormats nationalNumber:phoneNumber];
  1753. if (formattingPattern == nil) {
  1754. return phoneNumber;
  1755. }
  1756. return [self formatNsnUsingPattern:phoneNumber
  1757. formattingPattern:formattingPattern
  1758. numberFormat:numberFormat
  1759. carrierCode:opt_carrierCode];
  1760. }
  1761. /**
  1762. * - param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the
  1763. * available formats the phone number could be formatted into.
  1764. * - param {string} nationalNumber a string of characters representing a phone
  1765. * number.
  1766. * @return {i18n.phonenumbers.NumberFormat}
  1767. * @private
  1768. */
  1769. - (NBNumberFormat *)chooseFormattingPatternForNumber:(NSArray *)availableFormats
  1770. nationalNumber:(NSString *)nationalNumber {
  1771. for (NBNumberFormat *numFormat in availableFormats) {
  1772. NSUInteger size = [numFormat.leadingDigitsPatterns count];
  1773. // We always use the last leading_digits_pattern, as it is the most detailed.
  1774. if (size == 0 ||
  1775. [self stringPositionByRegex:nationalNumber
  1776. regex:[numFormat.leadingDigitsPatterns lastObject]] == 0) {
  1777. if ([self matchesEntirely:numFormat.pattern string:nationalNumber]) {
  1778. return numFormat;
  1779. }
  1780. }
  1781. }
  1782. return nil;
  1783. }
  1784. /**
  1785. * Note that carrierCode is optional - if nil or an empty string, no carrier
  1786. * code replacement will take place.
  1787. *
  1788. * - param {string} nationalNumber a string of characters representing a phone
  1789. * number.
  1790. * - param {i18n.phonenumbers.NumberFormat} formattingPattern the formatting rule
  1791. * the phone number should be formatted into.
  1792. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  1793. * phone number should be formatted into.
  1794. * - param {string=} opt_carrierCode
  1795. * @return {string} the formatted phone number.
  1796. * @private
  1797. */
  1798. - (NSString *)formatNsnUsingPattern:(NSString *)nationalNumber
  1799. formattingPattern:(NBNumberFormat *)formattingPattern
  1800. numberFormat:(NBEPhoneNumberFormat)numberFormat
  1801. carrierCode:(NSString *)opt_carrierCode {
  1802. NSString *numberFormatRule = formattingPattern.format;
  1803. NSString *domesticCarrierCodeFormattingRule = formattingPattern.domesticCarrierCodeFormattingRule;
  1804. NSString *formattedNationalNumber = @"";
  1805. if (numberFormat == NBEPhoneNumberFormatNATIONAL && [NBMetadataHelper hasValue:opt_carrierCode] &&
  1806. domesticCarrierCodeFormattingRule.length > 0) {
  1807. // Replace the $CC in the formatting rule with the desired carrier code.
  1808. NSString *carrierCodeFormattingRule =
  1809. [self replaceStringByRegex:domesticCarrierCodeFormattingRule
  1810. regex:CC_PATTERN
  1811. withTemplate:opt_carrierCode];
  1812. // Now replace the $FG in the formatting rule with the first group and
  1813. // the carrier code combined in the appropriate way.
  1814. numberFormatRule = [self replaceFirstStringByRegex:numberFormatRule
  1815. regex:FIRST_GROUP_PATTERN
  1816. withTemplate:carrierCodeFormattingRule];
  1817. formattedNationalNumber = [self replaceStringByRegex:nationalNumber
  1818. regex:formattingPattern.pattern
  1819. withTemplate:numberFormatRule];
  1820. } else {
  1821. // Use the national prefix formatting rule instead.
  1822. NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;
  1823. if (numberFormat == NBEPhoneNumberFormatNATIONAL &&
  1824. [NBMetadataHelper hasValue:nationalPrefixFormattingRule]) {
  1825. NSString *replacePattern = [self replaceFirstStringByRegex:numberFormatRule
  1826. regex:FIRST_GROUP_PATTERN
  1827. withTemplate:nationalPrefixFormattingRule];
  1828. formattedNationalNumber = [self replaceStringByRegex:nationalNumber
  1829. regex:formattingPattern.pattern
  1830. withTemplate:replacePattern];
  1831. } else {
  1832. formattedNationalNumber = [self replaceStringByRegex:nationalNumber
  1833. regex:formattingPattern.pattern
  1834. withTemplate:numberFormatRule];
  1835. }
  1836. }
  1837. if (numberFormat == NBEPhoneNumberFormatRFC3966) {
  1838. // Strip any leading punctuation.
  1839. formattedNationalNumber =
  1840. [self replaceStringByRegex:formattedNationalNumber
  1841. regex:[NSString stringWithFormat:@"^%@", SEPARATOR_PATTERN]
  1842. withTemplate:@""];
  1843. // Replace the rest with a dash between each number group.
  1844. formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber
  1845. regex:SEPARATOR_PATTERN
  1846. withTemplate:@"-"];
  1847. }
  1848. return formattedNationalNumber;
  1849. }
  1850. /**
  1851. * Gets a valid number for the specified region.
  1852. *
  1853. * - param {string} regionCode the region for which an example number is needed.
  1854. * @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the
  1855. * specified region. Returns nil when the metadata does not contain such
  1856. * information, or the region 001 is passed in. For 001 (representing non-
  1857. * geographical numbers), call {@link #getExampleNumberForNonGeoEntity}
  1858. * instead.
  1859. */
  1860. - (NBPhoneNumber *)getExampleNumber:(NSString *)regionCode error:(NSError *__autoreleasing *)error {
  1861. NBPhoneNumber *res =
  1862. [self getExampleNumberForType:regionCode type:NBEPhoneNumberTypeFIXED_LINE error:error];
  1863. return res;
  1864. }
  1865. /**
  1866. * Gets a valid number for the specified region and number type.
  1867. *
  1868. * - param {string} regionCode the region for which an example number is needed.
  1869. * - param {i18n.phonenumbers.PhoneNumberType} type the type of number that is
  1870. * needed.
  1871. * @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified
  1872. * region and type. Returns nil when the metadata does not contain such
  1873. * information or if an invalid region or region 001 was entered.
  1874. * For 001 (representing non-geographical numbers), call
  1875. * {@link #getExampleNumberForNonGeoEntity} instead.
  1876. */
  1877. - (NBPhoneNumber *)getExampleNumberForType:(NSString *)regionCode
  1878. type:(NBEPhoneNumberType)type
  1879. error:(NSError *__autoreleasing *)error {
  1880. NBPhoneNumber *res = nil;
  1881. if ([self isValidRegionCode:regionCode] == NO) {
  1882. return nil;
  1883. }
  1884. NBPhoneNumberDesc *desc =
  1885. [self getNumberDescByType:[self.helper getMetadataForRegion:regionCode] type:type];
  1886. if ([NBMetadataHelper hasValue:desc.exampleNumber]) {
  1887. return [self parse:desc.exampleNumber defaultRegion:regionCode error:error];
  1888. }
  1889. return res;
  1890. }
  1891. /**
  1892. * Gets a valid number for the specified country calling code for a
  1893. * non-geographical entity.
  1894. *
  1895. * - param {number} countryCallingCode the country calling code for a
  1896. * non-geographical entity.
  1897. * @return {i18n.phonenumbers.PhoneNumber} a valid number for the
  1898. * non-geographical entity. Returns nil when the metadata does not contain
  1899. * such information, or the country calling code passed in does not belong
  1900. * to a non-geographical entity.
  1901. */
  1902. - (NBPhoneNumber *)getExampleNumberForNonGeoEntity:(NSNumber *)countryCallingCode
  1903. error:(NSError *__autoreleasing *)error {
  1904. NBPhoneNumber *res = nil;
  1905. NBPhoneMetaData *metadata = [self.helper getMetadataForNonGeographicalRegion:countryCallingCode];
  1906. if (metadata != nil) {
  1907. NSString *fetchedExampleNumber = nil;
  1908. if ([NBMetadataHelper hasValue:metadata.mobile.exampleNumber]) {
  1909. fetchedExampleNumber = metadata.mobile.exampleNumber;
  1910. } else if ([NBMetadataHelper hasValue:metadata.tollFree.exampleNumber]) {
  1911. fetchedExampleNumber = metadata.tollFree.exampleNumber;
  1912. } else if ([NBMetadataHelper hasValue:metadata.sharedCost.exampleNumber]) {
  1913. fetchedExampleNumber = metadata.sharedCost.exampleNumber;
  1914. } else if ([NBMetadataHelper hasValue:metadata.voip.exampleNumber]) {
  1915. fetchedExampleNumber = metadata.voip.exampleNumber;
  1916. } else if ([NBMetadataHelper hasValue:metadata.voicemail.exampleNumber]) {
  1917. fetchedExampleNumber = metadata.voicemail.exampleNumber;
  1918. } else if ([NBMetadataHelper hasValue:metadata.uan.exampleNumber]) {
  1919. fetchedExampleNumber = metadata.uan.exampleNumber;
  1920. } else if ([NBMetadataHelper hasValue:metadata.premiumRate.exampleNumber]) {
  1921. fetchedExampleNumber = metadata.premiumRate.exampleNumber;
  1922. }
  1923. if (fetchedExampleNumber != nil) {
  1924. NSString *callCode =
  1925. [NSString stringWithFormat:@"+%@%@", countryCallingCode, fetchedExampleNumber];
  1926. res = [self parse:callCode defaultRegion:NB_UNKNOWN_REGION error:error];
  1927. }
  1928. }
  1929. return res;
  1930. }
  1931. /**
  1932. * Gets the formatted extension of a phone number, if the phone number had an
  1933. * extension specified. If not, it returns an empty string.
  1934. *
  1935. * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have
  1936. * an extension.
  1937. * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
  1938. * region that we think this number is from.
  1939. * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
  1940. * phone number should be formatted into.
  1941. * @return {string} the formatted extension if any.
  1942. * @private
  1943. */
  1944. - (NSString *)maybeGetFormattedExtension:(NBPhoneNumber *)number
  1945. metadata:(NBPhoneMetaData *)metadata
  1946. numberFormat:(NBEPhoneNumberFormat)numberFormat {
  1947. if ([NBMetadataHelper hasValue:number.extension] == NO) {
  1948. return @"";
  1949. } else {
  1950. if (numberFormat == NBEPhoneNumberFormatRFC3966) {
  1951. return [NSString stringWithFormat:@"%@%@", RFC3966_EXTN_PREFIX, number.extension];
  1952. } else {
  1953. if ([NBMetadataHelper hasValue:metadata.preferredExtnPrefix]) {
  1954. return [NSString stringWithFormat:@"%@%@", metadata.preferredExtnPrefix, number.extension];
  1955. } else {
  1956. return [NSString stringWithFormat:@"%@%@", DEFAULT_EXTN_PREFIX, number.extension];
  1957. }
  1958. }
  1959. }
  1960. }
  1961. /**
  1962. * - param {i18n.phonenumbers.PhoneMetadata} metadata
  1963. * - param {i18n.phonenumbers.PhoneNumberType} type
  1964. * @return {i18n.phonenumbers.PhoneNumberDesc}
  1965. * @private
  1966. */
  1967. - (NBPhoneNumberDesc *)getNumberDescByType:(NBPhoneMetaData *)metadata
  1968. type:(NBEPhoneNumberType)type {
  1969. switch (type) {
  1970. case NBEPhoneNumberTypePREMIUM_RATE:
  1971. return metadata.premiumRate;
  1972. case NBEPhoneNumberTypeTOLL_FREE:
  1973. return metadata.tollFree;
  1974. case NBEPhoneNumberTypeMOBILE:
  1975. if (metadata.mobile == nil) return metadata.generalDesc;
  1976. return metadata.mobile;
  1977. case NBEPhoneNumberTypeFIXED_LINE:
  1978. case NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE:
  1979. if (metadata.fixedLine == nil) return metadata.generalDesc;
  1980. return metadata.fixedLine;
  1981. case NBEPhoneNumberTypeSHARED_COST:
  1982. return metadata.sharedCost;
  1983. case NBEPhoneNumberTypeVOIP:
  1984. return metadata.voip;
  1985. case NBEPhoneNumberTypePERSONAL_NUMBER:
  1986. return metadata.personalNumber;
  1987. case NBEPhoneNumberTypePAGER:
  1988. return metadata.pager;
  1989. case NBEPhoneNumberTypeUAN:
  1990. return metadata.uan;
  1991. case NBEPhoneNumberTypeVOICEMAIL:
  1992. return metadata.voicemail;
  1993. default:
  1994. return metadata.generalDesc;
  1995. }
  1996. }
  1997. /**
  1998. * Gets the type of a phone number.
  1999. *
  2000. * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
  2001. * to know the type.
  2002. * @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number.
  2003. */
  2004. - (NBEPhoneNumberType)getNumberType:(NBPhoneNumber *)phoneNumber {
  2005. NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
  2006. NBPhoneMetaData *metadata =
  2007. [self getMetadataForRegionOrCallingCode:phoneNumber.countryCode regionCode:regionCode];
  2008. if (metadata == nil) {
  2009. return NBEPhoneNumberTypeUNKNOWN;
  2010. }
  2011. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];
  2012. return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata];
  2013. }
  2014. /**
  2015. * - param {string} nationalNumber
  2016. * - param {i18n.phonenumbers.PhoneMetadata} metadata
  2017. * @return {i18n.phonenumbers.PhoneNumberType}
  2018. * @private
  2019. */
  2020. - (NBEPhoneNumberType)getNumberTypeHelper:(NSString *)nationalNumber
  2021. metadata:(NBPhoneMetaData *)metadata {
  2022. NBPhoneNumberDesc *generalNumberDesc = metadata.generalDesc;
  2023. if ([self isNumberMatchingDesc:nationalNumber numberDesc:generalNumberDesc] == NO) {
  2024. return NBEPhoneNumberTypeUNKNOWN;
  2025. }
  2026. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.premiumRate]) {
  2027. return NBEPhoneNumberTypePREMIUM_RATE;
  2028. }
  2029. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.tollFree]) {
  2030. return NBEPhoneNumberTypeTOLL_FREE;
  2031. }
  2032. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.sharedCost]) {
  2033. return NBEPhoneNumberTypeSHARED_COST;
  2034. }
  2035. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voip]) {
  2036. return NBEPhoneNumberTypeVOIP;
  2037. }
  2038. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.personalNumber]) {
  2039. return NBEPhoneNumberTypePERSONAL_NUMBER;
  2040. }
  2041. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.pager]) {
  2042. return NBEPhoneNumberTypePAGER;
  2043. }
  2044. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.uan]) {
  2045. return NBEPhoneNumberTypeUAN;
  2046. }
  2047. if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voicemail]) {
  2048. return NBEPhoneNumberTypeVOICEMAIL;
  2049. }
  2050. BOOL isFixedLine = [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.fixedLine];
  2051. if (isFixedLine) {
  2052. if (metadata.sameMobileAndFixedLinePattern) {
  2053. return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
  2054. } else if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) {
  2055. return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
  2056. }
  2057. return NBEPhoneNumberTypeFIXED_LINE;
  2058. }
  2059. // Otherwise, test to see if the number is mobile. Only do this if certain
  2060. // that the patterns for mobile and fixed line aren't the same.
  2061. if ([metadata sameMobileAndFixedLinePattern] == NO &&
  2062. [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) {
  2063. return NBEPhoneNumberTypeMOBILE;
  2064. }
  2065. return NBEPhoneNumberTypeUNKNOWN;
  2066. }
  2067. /**
  2068. * - param {string} nationalNumber
  2069. * - param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
  2070. * @return {boolean}
  2071. * @private
  2072. */
  2073. - (BOOL)isNumberMatchingDesc:(NSString *)nationalNumber numberDesc:(NBPhoneNumberDesc *)numberDesc {
  2074. NSNumber *actualLength = [NSNumber numberWithUnsignedInteger:nationalNumber.length];
  2075. if (numberDesc.possibleLength.count > 0 &&
  2076. [numberDesc.possibleLength indexOfObject:actualLength] == NSNotFound) {
  2077. return NO;
  2078. }
  2079. return [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber];
  2080. }
  2081. /**
  2082. * Tests whether a phone number matches a valid pattern. Note this doesn't
  2083. * verify the number is actually in use, which is impossible to tell by just
  2084. * looking at a number itself.
  2085. *
  2086. * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
  2087. * to validate.
  2088. * @return {boolean} a boolean that indicates whether the number is of a valid
  2089. * pattern.
  2090. */
  2091. - (BOOL)isValidNumber:(NBPhoneNumber *)number {
  2092. NSString *regionCode = [self getRegionCodeForNumber:number];
  2093. return [self isValidNumberForRegion:number regionCode:regionCode];
  2094. }
  2095. /**
  2096. * Tests whether a phone number is valid for a certain region. Note this doesn't
  2097. * verify the number is actually in use, which is impossible to tell by just
  2098. * looking at a number itself. If the country calling code is not the same as
  2099. * the country calling code for the region, this immediately exits with NO.
  2100. * After this, the specific number pattern rules for the region are examined.
  2101. * This is useful for determining for example whether a particular number is
  2102. * valid for Canada, rather than just a valid NANPA number.
  2103. * Warning: In most cases, you want to use {@link #isValidNumber} instead. For
  2104. * example, this method will mark numbers from British Crown dependencies such
  2105. * as the Isle of Man as invalid for the region "GB" (United Kingdom), since it
  2106. * has its own region code, "IM", which may be undesirable.
  2107. *
  2108. * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
  2109. * to validate.
  2110. * - param {?string} regionCode the region that we want to validate the phone
  2111. * number for.
  2112. * @return {boolean} a boolean that indicates whether the number is of a valid
  2113. * pattern.
  2114. */
  2115. - (BOOL)isValidNumberForRegion:(NBPhoneNumber *)number regionCode:(NSString *)regionCode {
  2116. NSNumber *countryCode = [number.countryCode copy];
  2117. NBPhoneMetaData *metadata =
  2118. [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  2119. if (metadata == nil ||
  2120. ([NB_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] == NO &&
  2121. ![countryCode isEqualToNumber:[self getCountryCodeForValidRegion:regionCode error:nil]])) {
  2122. // Either the region code was invalid, or the country calling code for this
  2123. // number does not match that of the region code.
  2124. return NO;
  2125. }
  2126. NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc;
  2127. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  2128. // For regions where we don't have metadata for PhoneNumberDesc, we treat any
  2129. // number passed in as a valid number if its national significant number is
  2130. // between the minimum and maximum lengths defined by ITU for a national
  2131. // significant number.
  2132. if ([NBMetadataHelper hasValue:generalNumDesc.nationalNumberPattern] == NO) {
  2133. NSUInteger numberLength = nationalSignificantNumber.length;
  2134. return numberLength > MIN_LENGTH_FOR_NSN_ && numberLength <= MAX_LENGTH_FOR_NSN_;
  2135. }
  2136. return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata] !=
  2137. NBEPhoneNumberTypeUNKNOWN;
  2138. }
  2139. /**
  2140. * Returns the region where a phone number is from. This could be used for
  2141. * geocoding at the region level.
  2142. *
  2143. * - param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin
  2144. * we want to know.
  2145. * @return {?string} the region where the phone number is from, or nil
  2146. * if no region matches this calling code.
  2147. */
  2148. - (NSString *)getRegionCodeForNumber:(NBPhoneNumber *)phoneNumber {
  2149. if (phoneNumber == nil) {
  2150. return nil;
  2151. }
  2152. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:phoneNumber.countryCode];
  2153. if (regionCodes == nil || [regionCodes count] <= 0) {
  2154. return nil;
  2155. }
  2156. if ([regionCodes count] == 1) {
  2157. return [regionCodes objectAtIndex:0];
  2158. } else {
  2159. return [self getRegionCodeForNumberFromRegionList:phoneNumber regionCodes:regionCodes];
  2160. }
  2161. }
  2162. /**
  2163. * - param {i18n.phonenumbers.PhoneNumber} number
  2164. * - param {Array.<string>} regionCodes
  2165. * @return {?string}
  2166. * @private
  2167. */
  2168. - (NSString *)getRegionCodeForNumberFromRegionList:(NBPhoneNumber *)phoneNumber
  2169. regionCodes:(NSArray *)regionCodes {
  2170. NSString *nationalNumber = [self getNationalSignificantNumber:phoneNumber];
  2171. NSUInteger regionCodesCount = [regionCodes count];
  2172. NBMetadataHelper *helper = self.helper;
  2173. for (NSUInteger i = 0; i < regionCodesCount; i++) {
  2174. NSString *regionCode = [regionCodes objectAtIndex:i];
  2175. NBPhoneMetaData *metadata = [helper getMetadataForRegion:regionCode];
  2176. if ([NBMetadataHelper hasValue:metadata.leadingDigits]) {
  2177. if ([self stringPositionByRegex:nationalNumber regex:metadata.leadingDigits] == 0) {
  2178. return regionCode;
  2179. }
  2180. } else if ([self getNumberTypeHelper:nationalNumber metadata:metadata] !=
  2181. NBEPhoneNumberTypeUNKNOWN) {
  2182. return regionCode;
  2183. }
  2184. }
  2185. return nil;
  2186. }
  2187. /**
  2188. * Returns the region code that matches the specific country calling code. In
  2189. * the case of no region code being found, ZZ will be returned. In the case of
  2190. * multiple regions, the one designated in the metadata as the 'main' region for
  2191. * this calling code will be returned.
  2192. *
  2193. * - param {number} countryCallingCode the country calling code.
  2194. * @return {string}
  2195. */
  2196. - (NSString *)getRegionCodeForCountryCode:(NSNumber *)countryCallingCode {
  2197. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  2198. return regionCodes == nil ? NB_UNKNOWN_REGION : [regionCodes objectAtIndex:0];
  2199. }
  2200. /**
  2201. * Returns a list with the region codes that match the specific country calling
  2202. * code. For non-geographical country calling codes, the region code 001 is
  2203. * returned. Also, in the case of no region code being found, an empty list is
  2204. * returned.
  2205. *
  2206. * - param {number} countryCallingCode the country calling code.
  2207. * @return {Array.<string>}
  2208. */
  2209. - (NSArray *)getRegionCodesForCountryCode:(NSNumber *)countryCallingCode {
  2210. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  2211. return regionCodes == nil ? nil : regionCodes;
  2212. }
  2213. /**
  2214. * Returns the country calling code for a specific region. For example, this
  2215. * would be 1 for the United States, and 64 for New Zealand.
  2216. *
  2217. * - param {?string} regionCode the region that we want to get the country
  2218. * calling code for.
  2219. * @return {number} the country calling code for the region denoted by
  2220. * regionCode.
  2221. */
  2222. - (NSNumber *)getCountryCodeForRegion:(NSString *)regionCode {
  2223. if ([self isValidRegionCode:regionCode] == NO) {
  2224. return @0;
  2225. }
  2226. NSError *error = nil;
  2227. NSNumber *res = [self getCountryCodeForValidRegion:regionCode error:&error];
  2228. if (error != nil) {
  2229. return @0;
  2230. }
  2231. return res;
  2232. }
  2233. /**
  2234. * Returns the country calling code for a specific region. For example, this
  2235. * would be 1 for the United States, and 64 for New Zealand. Assumes the region
  2236. * is already valid.
  2237. *
  2238. * - param {?string} regionCode the region that we want to get the country
  2239. * calling code for.
  2240. * @return {number} the country calling code for the region denoted by
  2241. * regionCode.
  2242. * @throws {string} if the region is invalid
  2243. * @private
  2244. */
  2245. - (NSNumber *)getCountryCodeForValidRegion:(NSString *)regionCode error:(NSError **)error {
  2246. NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
  2247. if (metadata == nil) {
  2248. NSDictionary *userInfo = [NSDictionary
  2249. dictionaryWithObject:[NSString stringWithFormat:@"Invalid region code:%@", regionCode]
  2250. forKey:NSLocalizedDescriptionKey];
  2251. if (error != NULL) {
  2252. (*error) = [NSError errorWithDomain:@"INVALID_REGION_CODE" code:0 userInfo:userInfo];
  2253. }
  2254. return @-1;
  2255. }
  2256. return metadata.countryCode;
  2257. }
  2258. /**
  2259. * Returns the national dialling prefix for a specific region. For example, this
  2260. * would be 1 for the United States, and 0 for New Zealand. Set stripNonDigits
  2261. * to NO to strip symbols like '~' (which indicates a wait for a dialling
  2262. * tone) from the prefix returned. If no national prefix is present, we return
  2263. * nil.
  2264. *
  2265. * <p>Warning: Do not use this method for do-your-own formatting - for some
  2266. * regions, the national dialling prefix is used only for certain types of
  2267. * numbers. Use the library's formatting functions to prefix the national prefix
  2268. * when required.
  2269. *
  2270. * - param {?string} regionCode the region that we want to get the dialling
  2271. * prefix for.
  2272. * - param {boolean} stripNonDigits NO to strip non-digits from the national
  2273. * dialling prefix.
  2274. * @return {?string} the dialling prefix for the region denoted by
  2275. * regionCode.
  2276. */
  2277. - (NSString *)getNddPrefixForRegion:(NSString *)regionCode stripNonDigits:(BOOL)stripNonDigits {
  2278. NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
  2279. if (metadata == nil) {
  2280. return nil;
  2281. }
  2282. NSString *nationalPrefix = metadata.nationalPrefix;
  2283. // If no national prefix was found, we return nil.
  2284. if (nationalPrefix.length == 0) {
  2285. return nil;
  2286. }
  2287. if (stripNonDigits) {
  2288. // Note: if any other non-numeric symbols are ever used in national
  2289. // prefixes, these would have to be removed here as well.
  2290. nationalPrefix = [nationalPrefix stringByReplacingOccurrencesOfString:@"~" withString:@""];
  2291. }
  2292. return nationalPrefix;
  2293. }
  2294. /**
  2295. * Checks if this is a region under the North American Numbering Plan
  2296. * Administration (NANPA).
  2297. *
  2298. * - param {?string} regionCode the ISO 3166-1 two-letter region code.
  2299. * @return {boolean} NO if regionCode is one of the regions under NANPA.
  2300. */
  2301. - (BOOL)isNANPACountry:(NSString *)regionCode {
  2302. BOOL isExists = NO;
  2303. NSArray *res = [NBMetadataHelper
  2304. regionCodeFromCountryCode:[NSNumber numberWithUnsignedInteger:NANPA_COUNTRY_CODE_]];
  2305. for (NSString *inRegionCode in res) {
  2306. if ([inRegionCode isEqualToString:regionCode.uppercaseString]) {
  2307. isExists = YES;
  2308. }
  2309. }
  2310. return regionCode != nil && isExists;
  2311. }
  2312. /**
  2313. * Checks whether countryCode represents the country calling code from a region
  2314. * whose national significant number could contain a leading zero. An example of
  2315. * such a region is Italy. Returns NO if no metadata for the country is
  2316. * found.
  2317. *
  2318. * - param {number} countryCallingCode the country calling code.
  2319. * @return {boolean}
  2320. */
  2321. - (BOOL)isLeadingZeroPossible:(NSNumber *)countryCallingCode {
  2322. NBPhoneMetaData *mainMetadataForCallingCode = [self
  2323. getMetadataForRegionOrCallingCode:countryCallingCode
  2324. regionCode:[self getRegionCodeForCountryCode:countryCallingCode]];
  2325. return mainMetadataForCallingCode != nil && mainMetadataForCallingCode.leadingZeroPossible;
  2326. }
  2327. /**
  2328. * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT.
  2329. * A valid vanity number will start with at least 3 digits and will have three
  2330. * or more alpha characters. This does not do region-specific checks - to work
  2331. * out if this number is actually valid for a region, it should be parsed and
  2332. * methods such as {@link #isPossibleNumberWithReason} and
  2333. * {@link #isValidNumber} should be used.
  2334. *
  2335. * - param {string} number the number that needs to be checked.
  2336. * @return {boolean} NO if the number is a valid vanity number.
  2337. */
  2338. - (BOOL)isAlphaNumber:(NSString *)number {
  2339. if ([self isViablePhoneNumber:number] == NO) {
  2340. // Number is too short, or doesn't match the basic phone number pattern.
  2341. return NO;
  2342. }
  2343. number = NormalizeNonBreakingSpace(number);
  2344. /** @type {!goog.string.StringBuffer} */
  2345. NSString *strippedNumber = [number copy];
  2346. [self maybeStripExtension:&strippedNumber];
  2347. return [self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:strippedNumber];
  2348. }
  2349. /**
  2350. * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of
  2351. * returning the reason for failure, this method returns a boolean value.
  2352. *
  2353. * - param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
  2354. * checked.
  2355. * @return {boolean} NO if the number is possible.
  2356. */
  2357. - (BOOL)isPossibleNumber:(NBPhoneNumber *)number error:(NSError **)error {
  2358. BOOL res = NO;
  2359. @try {
  2360. res = [self isPossibleNumber:number];
  2361. } @catch (NSException *exception) {
  2362. NSDictionary *userInfo =
  2363. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  2364. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  2365. }
  2366. return res;
  2367. }
  2368. - (BOOL)isPossibleNumber:(NBPhoneNumber *)number {
  2369. return [self isPossibleNumberWithReason:number] == NBEValidationResultIS_POSSIBLE;
  2370. }
  2371. /**
  2372. * Helper method to check a number against possible lengths for this region, based on the metadata
  2373. * being passed in, and determine whether it matches, or is too short or too long.
  2374. */
  2375. - (NBEValidationResult)validateNumberLength:(NSString *)number
  2376. metadata:(NBPhoneMetaData *)metadata {
  2377. return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeUNKNOWN];
  2378. }
  2379. /**
  2380. * Helper method to check a number against possible lengths for this number type, and determine
  2381. * whether it matches, or is too short or too long.
  2382. */
  2383. - (NBEValidationResult)validateNumberLength:(NSString *)number
  2384. metadata:(NBPhoneMetaData *)metadata
  2385. type:(NBEPhoneNumberType)type {
  2386. NBPhoneNumberDesc *descForType = [self getNumberDescByType:metadata type:type];
  2387. // There should always be "possibleLengths" set for every element.
  2388. // For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
  2389. // as the parent, this is missing, so we fall back to the general desc (where no numbers of the
  2390. // type exist at all, there is one possible length (-1) which is guaranteed not to match the
  2391. // length of any real phone number).
  2392. NSArray<NSNumber *> *possibleLengths = [descForType.possibleLength count] == 0
  2393. ? metadata.generalDesc.possibleLength
  2394. : descForType.possibleLength;
  2395. NSArray<NSNumber *> *localLengths = descForType.possibleLengthLocalOnly;
  2396. if (type == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE) {
  2397. if ([self descHasPossibleNumberData:[self getNumberDescByType:metadata
  2398. type:NBEPhoneNumberTypeFIXED_LINE]]) {
  2399. // The rare case has been encountered where no fixedLine data is available (true for some
  2400. // non-geographical entities), so we just check mobile.
  2401. return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeMOBILE];
  2402. } else {
  2403. NBPhoneNumberDesc *mobileDesc =
  2404. [self getNumberDescByType:metadata type:NBEPhoneNumberTypeMOBILE];
  2405. if ([self descHasPossibleNumberData:mobileDesc]) {
  2406. // Merge the mobile data in if there was any. We have to make a copy to do this.
  2407. // Note that when adding the possible lengths from mobile, we have to again check they
  2408. // aren't empty since if they are this indicates they are the same as the general desc and
  2409. // should be obtained from there.
  2410. NSArray *combinedArray =
  2411. [possibleLengths arrayByAddingObjectsFromArray:[mobileDesc.possibleLength count] == 0
  2412. ? metadata.generalDesc.possibleLength
  2413. : mobileDesc.possibleLength];
  2414. // The current list is sorted; we need to merge in the new list and re-sort (duplicates
  2415. // are okay). Sorting isn't so expensive because the lists are very small.
  2416. possibleLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];
  2417. if (![localLengths count]) {
  2418. localLengths = mobileDesc.possibleLengthLocalOnly;
  2419. } else {
  2420. NSArray *combinedArray =
  2421. [localLengths arrayByAddingObjectsFromArray:mobileDesc.possibleLengthLocalOnly];
  2422. localLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];
  2423. }
  2424. }
  2425. }
  2426. }
  2427. // If the type is not supported at all (indicated by the possible lengths containing -1 at this
  2428. // point) we return invalid length.
  2429. if ([possibleLengths.firstObject isEqualToNumber:@(-1)]) {
  2430. return NBEValidationResultINVALID_LENGTH;
  2431. }
  2432. NSNumber *actualLength = @(number.length);
  2433. // This is safe because there is never an overlap beween the possible lengths and the local-only
  2434. // lengths; this is checked at build time.
  2435. if ([localLengths containsObject:actualLength]) {
  2436. return NBEValidationResultIS_POSSIBLE_LOCAL_ONLY;
  2437. }
  2438. NSNumber *minimumLength = possibleLengths.firstObject;
  2439. NSComparisonResult comparisionResult = [minimumLength compare:actualLength];
  2440. if (comparisionResult == NSOrderedSame) {
  2441. return NBEValidationResultIS_POSSIBLE;
  2442. } else if (comparisionResult == NSOrderedDescending) {
  2443. return NBEValidationResultTOO_SHORT;
  2444. } else if ([possibleLengths.lastObject compare:actualLength] == NSOrderedAscending) {
  2445. return NBEValidationResultTOO_LONG;
  2446. }
  2447. // We skip the first element; we've already checked it.
  2448. NSArray *possibleLengthsSubarray =
  2449. [possibleLengths subarrayWithRange:NSMakeRange(1, possibleLengths.count - 1)];
  2450. return [possibleLengthsSubarray containsObject:actualLength] ? NBEValidationResultIS_POSSIBLE
  2451. : NBEValidationResultINVALID_LENGTH;
  2452. }
  2453. /**
  2454. * Helper method to check a number against a particular pattern and determine
  2455. * whether it matches, or is too short or too long. Currently, if a number
  2456. * pattern suggests that numbers of length 7 and 10 are possible, and a number
  2457. * in between these possible lengths is entered, such as of length 8, this will
  2458. * return TOO_LONG.
  2459. *
  2460. * - param {string} number
  2461. * - param {i18n.phonenumbers.PhoneNumberDesc} phoneNumberDesc
  2462. * - return {i18n.phonenumbers.PhoneNumberUtil.ValidationResult}
  2463. * @private
  2464. */
  2465. - (NBEValidationResult)testNumberLength:(NSString *)number
  2466. desc:(NBPhoneNumberDesc *)phoneNumberDesc {
  2467. NSArray *possibleLengths = phoneNumberDesc.possibleLength;
  2468. NSArray *localLengths = phoneNumberDesc.possibleLengthLocalOnly;
  2469. NSUInteger actualLength = number.length;
  2470. if ([localLengths containsObject:@(actualLength)]) {
  2471. return NBEValidationResultIS_POSSIBLE;
  2472. }
  2473. // There should always be "possibleLengths" set for every element. This will
  2474. // be a build-time check once ShortNumberMetadata.xml is migrated to contain
  2475. // this information as well.
  2476. NSNumber *minimumLength = possibleLengths[0];
  2477. if (minimumLength.unsignedIntegerValue == actualLength) {
  2478. return NBEValidationResultIS_POSSIBLE;
  2479. } else if (minimumLength.unsignedIntegerValue > actualLength) {
  2480. return NBEValidationResultTOO_SHORT;
  2481. } else if (possibleLengths.count - 1 < possibleLengths.count) {
  2482. if (((NSNumber *)possibleLengths[possibleLengths.count - 1]).integerValue < actualLength) {
  2483. return NBEValidationResultTOO_LONG;
  2484. }
  2485. }
  2486. // Note that actually the number is not too long if possible_lengths does not
  2487. // contain the length: we know it is less than the highest possible number
  2488. // length, and higher than the lowest possible number length. However, we
  2489. // don't currently have an enum to express this, so we return TOO_LONG in the
  2490. // short-term.
  2491. // We skip the first element since we've already checked it.
  2492. return [possibleLengths containsObject:@(actualLength)] ? NBEValidationResultIS_POSSIBLE
  2493. : NBEValidationResultTOO_LONG;
  2494. }
  2495. /**
  2496. * Check whether a phone number is a possible number. It provides a more lenient
  2497. * check than {@link #isValidNumber} in the following sense:
  2498. * <ol>
  2499. * <li>It only checks the length of phone numbers. In particular, it doesn't
  2500. * check starting digits of the number.
  2501. * <li>It doesn't attempt to figure out the type of the number, but uses general
  2502. * rules which applies to all types of phone numbers in a region. Therefore, it
  2503. * is much faster than isValidNumber.
  2504. * <li>For fixed line numbers, many regions have the concept of area code, which
  2505. * together with subscriber number constitute the national significant number.
  2506. * It is sometimes okay to dial the subscriber number only when dialing in the
  2507. * same area. This function will return NO if the subscriber-number-only
  2508. * version is passed in. On the other hand, because isValidNumber validates
  2509. * using information on both starting digits (for fixed line numbers, that would
  2510. * most likely be area codes) and length (obviously includes the length of area
  2511. * codes for fixed line numbers), it will return NO for the
  2512. * subscriber-number-only version.
  2513. * </ol>
  2514. *
  2515. * - param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
  2516. * checked.
  2517. * @return {ValidationResult} a
  2518. * ValidationResult object which indicates whether the number is possible.
  2519. */
  2520. - (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber *)number
  2521. error:(NSError *__autoreleasing *)error {
  2522. NBEValidationResult res = NBEValidationResultUNKNOWN;
  2523. @try {
  2524. res = [self isPossibleNumberWithReason:number];
  2525. } @catch (NSException *exception) {
  2526. NSDictionary *userInfo =
  2527. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  2528. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  2529. }
  2530. return res;
  2531. }
  2532. - (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber *)number {
  2533. NSString *nationalNumber = [self getNationalSignificantNumber:number];
  2534. NSNumber *countryCode = number.countryCode;
  2535. // Note: For Russian Fed and NANPA numbers, we just use the rules from the
  2536. // default region (US or Russia) since the getRegionCodeForNumber will not
  2537. // work if the number is possible but not valid. This would need to be
  2538. // revisited if the possible number pattern ever differed between various
  2539. // regions within those plans.
  2540. if ([self hasValidCountryCallingCode:countryCode] == NO) {
  2541. return NBEValidationResultINVALID_COUNTRY_CODE;
  2542. }
  2543. NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
  2544. // Metadata cannot be nil because the country calling code is valid.
  2545. NBPhoneMetaData *metadata =
  2546. [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  2547. return [self testNumberLength:nationalNumber desc:metadata.generalDesc];
  2548. }
  2549. /**
  2550. * Check whether a phone number is a possible number given a number in the form
  2551. * of a string, and the region where the number could be dialed from. It
  2552. * provides a more lenient check than {@link #isValidNumber}. See
  2553. * {@link #isPossibleNumber} for details.
  2554. *
  2555. * <p>This method first parses the number, then invokes
  2556. * {@link #isPossibleNumber} with the resultant PhoneNumber object.
  2557. *
  2558. * - param {string} number the number that needs to be checked, in the form of a
  2559. * string.
  2560. * - param {string} regionDialingFrom the region that we are expecting the number
  2561. * to be dialed from.
  2562. * Note this is different from the region where the number belongs.
  2563. * For example, the number +1 650 253 0000 is a number that belongs to US.
  2564. * When written in this form, it can be dialed from any region. When it is
  2565. * written as 00 1 650 253 0000, it can be dialed from any region which uses
  2566. * an international dialling prefix of 00. When it is written as
  2567. * 650 253 0000, it can only be dialed from within the US, and when written
  2568. * as 253 0000, it can only be dialed from within a smaller area in the US
  2569. * (Mountain View, CA, to be more specific).
  2570. * @return {boolean} NO if the number is possible.
  2571. */
  2572. - (BOOL)isPossibleNumberString:(NSString *)number
  2573. regionDialingFrom:(NSString *)regionDialingFrom
  2574. error:(NSError **)error {
  2575. number = NormalizeNonBreakingSpace(number);
  2576. BOOL res =
  2577. [self isPossibleNumber:[self parse:number defaultRegion:regionDialingFrom error:error]];
  2578. return res;
  2579. }
  2580. /**
  2581. * Attempts to extract a valid number from a phone number that is too long to be
  2582. * valid, and resets the PhoneNumber object passed in to that valid version. If
  2583. * no valid number could be extracted, the PhoneNumber object passed in will not
  2584. * be modified.
  2585. * - param {i18n.phonenumbers.PhoneNumber} number a PhoneNumber object which
  2586. * contains a number that is too long to be valid.
  2587. * @return {boolean} NO if a valid phone number can be successfully extracted.
  2588. */
  2589. - (BOOL)truncateTooLongNumber:(NBPhoneNumber *)number {
  2590. if ([self isValidNumber:number]) {
  2591. return YES;
  2592. }
  2593. NBPhoneNumber *numberCopy = [number copy];
  2594. NSNumber *nationalNumber = number.nationalNumber;
  2595. do {
  2596. nationalNumber =
  2597. [NSNumber numberWithLongLong:(long long)floor(nationalNumber.unsignedLongLongValue / 10)];
  2598. numberCopy.nationalNumber = [nationalNumber copy];
  2599. if ([nationalNumber isEqualToNumber:@0] ||
  2600. [self isPossibleNumberWithReason:numberCopy] == NBEValidationResultTOO_SHORT) {
  2601. return NO;
  2602. }
  2603. } while ([self isValidNumber:numberCopy] == NO);
  2604. number.nationalNumber = nationalNumber;
  2605. return YES;
  2606. }
  2607. /**
  2608. * Extracts country calling code from fullNumber, returns it and places the
  2609. * remaining number in nationalNumber. It assumes that the leading plus sign or
  2610. * IDD has already been removed. Returns 0 if fullNumber doesn't start with a
  2611. * valid country calling code, and leaves nationalNumber unmodified.
  2612. *
  2613. * - param {!goog.string.StringBuffer} fullNumber
  2614. * - param {!goog.string.StringBuffer} nationalNumber
  2615. * @return {number}
  2616. */
  2617. - (NSNumber *)extractCountryCode:(NSString *)fullNumber nationalNumber:(NSString **)nationalNumber {
  2618. fullNumber = NormalizeNonBreakingSpace(fullNumber);
  2619. if ((fullNumber.length == 0) || ([[fullNumber substringToIndex:1] isEqualToString:@"0"])) {
  2620. // Country codes do not begin with a '0'.
  2621. return @0;
  2622. }
  2623. NSUInteger numberLength = fullNumber.length;
  2624. NSUInteger maxCountryCode = MAX_LENGTH_COUNTRY_CODE_;
  2625. if ([fullNumber hasPrefix:@"+"]) {
  2626. maxCountryCode = MAX_LENGTH_COUNTRY_CODE_ + 1;
  2627. }
  2628. for (NSUInteger i = 1; i <= maxCountryCode && i <= numberLength; ++i) {
  2629. NSString *subNumber = [fullNumber substringWithRange:NSMakeRange(0, i)];
  2630. NSNumber *potentialCountryCode = [NSNumber numberWithInteger:[subNumber integerValue]];
  2631. NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:potentialCountryCode];
  2632. if (regionCodes != nil && regionCodes.count > 0) {
  2633. if (nationalNumber != NULL) {
  2634. if ((*nationalNumber) == nil) {
  2635. (*nationalNumber) = [NSString stringWithFormat:@"%@", [fullNumber substringFromIndex:i]];
  2636. } else {
  2637. (*nationalNumber) = [NSString
  2638. stringWithFormat:@"%@%@", (*nationalNumber), [fullNumber substringFromIndex:i]];
  2639. }
  2640. }
  2641. return potentialCountryCode;
  2642. }
  2643. }
  2644. return @0;
  2645. }
  2646. /**
  2647. * Convenience method to get a list of what regions the library has metadata
  2648. * for.
  2649. * @return {!Array.<string>} region codes supported by the library.
  2650. */
  2651. - (NSArray *)getSupportedRegions {
  2652. NSArray *allKeys = [[NBMetadataHelper CCode2CNMap] allKeys];
  2653. NSPredicate *predicateIsNaN =
  2654. [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  2655. return isNan(evaluatedObject);
  2656. }];
  2657. NSArray *supportedRegions = [allKeys filteredArrayUsingPredicate:predicateIsNaN];
  2658. return supportedRegions;
  2659. }
  2660. /*
  2661. i18n.phonenumbers.PhoneNumberUtil.prototype.getSupportedRegions = function() {
  2662. return goog.array.filter(
  2663. Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
  2664. function(regionCode) {
  2665. return isNaN(regionCode);
  2666. });
  2667. };
  2668. */
  2669. /**
  2670. * Convenience method to get a list of what global network calling codes the
  2671. * library has metadata for.
  2672. * @return {!Array.<number>} global network calling codes supported by the
  2673. * library.
  2674. */
  2675. /*
  2676. i18n.phonenumbers.PhoneNumberUtil.prototype.
  2677. getSupportedGlobalNetworkCallingCodes = function() {
  2678. var callingCodesAsStrings = goog.array.filter(
  2679. Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
  2680. function(regionCode) {
  2681. return !isNaN(regionCode);
  2682. });
  2683. return goog.array.map(callingCodesAsStrings,
  2684. function(callingCode) {
  2685. return parseInt(callingCode, 10);
  2686. });
  2687. };
  2688. */
  2689. /**
  2690. * Tries to extract a country calling code from a number. This method will
  2691. * return zero if no country calling code is considered to be present. Country
  2692. * calling codes are extracted in the following ways:
  2693. * <ul>
  2694. * <li>by stripping the international dialing prefix of the region the person is
  2695. * dialing from, if this is present in the number, and looking at the next
  2696. * digits
  2697. * <li>by stripping the '+' sign if present and then looking at the next digits
  2698. * <li>by comparing the start of the number and the country calling code of the
  2699. * default region. If the number is not considered possible for the numbering
  2700. * plan of the default region initially, but starts with the country calling
  2701. * code of this region, validation will be reattempted after stripping this
  2702. * country calling code. If this number is considered a possible number, then
  2703. * the first digits will be considered the country calling code and removed as
  2704. * such.
  2705. * </ul>
  2706. *
  2707. * It will throw a i18n.phonenumbers.Error if the number starts with a '+' but
  2708. * the country calling code supplied after this does not match that of any known
  2709. * region.
  2710. *
  2711. * - param {string} number non-normalized telephone number that we wish to
  2712. * extract a country calling code from - may begin with '+'.
  2713. * - param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata
  2714. * about the region this number may be from.
  2715. * - param {!goog.string.StringBuffer} nationalNumber a string buffer to store
  2716. * the national significant number in, in the case that a country calling
  2717. * code was extracted. The number is appended to any existing contents. If
  2718. * no country calling code was extracted, this will be left unchanged.
  2719. * - param {boolean} keepRawInput NO if the country_code_source and
  2720. * preferred_carrier_code fields of phoneNumber should be populated.
  2721. * - param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object
  2722. * where the country_code and country_code_source need to be populated.
  2723. * Note the country_code is always populated, whereas country_code_source is
  2724. * only populated when keepCountryCodeSource is NO.
  2725. * @return {number} the country calling code extracted or 0 if none could be
  2726. * extracted.
  2727. * @throws {i18n.phonenumbers.Error}
  2728. */
  2729. - (NSNumber *)maybeExtractCountryCode:(NSString *)number
  2730. metadata:(NBPhoneMetaData *)defaultRegionMetadata
  2731. nationalNumber:(NSString **)nationalNumber
  2732. keepRawInput:(BOOL)keepRawInput
  2733. phoneNumber:(NBPhoneNumber **)phoneNumber
  2734. error:(NSError **)error {
  2735. if (nationalNumber == NULL || phoneNumber == NULL || number.length <= 0) {
  2736. return @0;
  2737. }
  2738. NSString *fullNumber = [number copy];
  2739. // Set the default prefix to be something that will never match.
  2740. NSString *possibleCountryIddPrefix = @"";
  2741. if (defaultRegionMetadata != nil) {
  2742. possibleCountryIddPrefix = defaultRegionMetadata.internationalPrefix;
  2743. }
  2744. if (possibleCountryIddPrefix == nil) {
  2745. possibleCountryIddPrefix = @"NonMatch";
  2746. }
  2747. /** @type {i18n.phonenumbers.PhoneNumber.CountryCodeSource} */
  2748. NBECountryCodeSource countryCodeSource =
  2749. [self maybeStripInternationalPrefixAndNormalize:&fullNumber
  2750. possibleIddPrefix:possibleCountryIddPrefix];
  2751. if (keepRawInput) {
  2752. (*phoneNumber).countryCodeSource = [NSNumber numberWithInteger:countryCodeSource];
  2753. }
  2754. if (countryCodeSource != NBECountryCodeSourceFROM_DEFAULT_COUNTRY) {
  2755. if (fullNumber.length <= MIN_LENGTH_FOR_NSN_) {
  2756. NSDictionary *userInfo = [NSDictionary
  2757. dictionaryWithObject:[NSString stringWithFormat:@"TOO_SHORT_AFTER_IDD:%@", fullNumber]
  2758. forKey:NSLocalizedDescriptionKey];
  2759. if (error != NULL) {
  2760. (*error) = [NSError errorWithDomain:@"TOO_SHORT_AFTER_IDD" code:0 userInfo:userInfo];
  2761. }
  2762. return @0;
  2763. }
  2764. NSNumber *potentialCountryCode =
  2765. [self extractCountryCode:fullNumber nationalNumber:nationalNumber];
  2766. if (![potentialCountryCode isEqualToNumber:@0]) {
  2767. (*phoneNumber).countryCode = potentialCountryCode;
  2768. return potentialCountryCode;
  2769. }
  2770. // If this fails, they must be using a strange country calling code that we
  2771. // don't recognize, or that doesn't exist.
  2772. NSDictionary *userInfo =
  2773. [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@",
  2774. potentialCountryCode]
  2775. forKey:NSLocalizedDescriptionKey];
  2776. if (error != NULL) {
  2777. (*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
  2778. }
  2779. return @0;
  2780. } else if (defaultRegionMetadata != nil) {
  2781. // Check to see if the number starts with the country calling code for the
  2782. // default region. If so, we remove the country calling code, and do some
  2783. // checks on the validity of the number before and after.
  2784. NSNumber *defaultCountryCode = defaultRegionMetadata.countryCode;
  2785. NSString *defaultCountryCodeString = [NSString stringWithFormat:@"%@", defaultCountryCode];
  2786. NSString *normalizedNumber = [fullNumber copy];
  2787. if ([normalizedNumber hasPrefix:defaultCountryCodeString]) {
  2788. NSString *potentialNationalNumber =
  2789. [normalizedNumber substringFromIndex:defaultCountryCodeString.length];
  2790. NBPhoneNumberDesc *generalDesc = defaultRegionMetadata.generalDesc;
  2791. NSString *validNumberPattern = generalDesc.nationalNumberPattern;
  2792. // Passing null since we don't need the carrier code.
  2793. [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber
  2794. metadata:defaultRegionMetadata
  2795. carrierCode:nil];
  2796. NSString *potentialNationalNumberStr = [potentialNationalNumber copy];
  2797. // If the number was not valid before but is valid now, or if it was too
  2798. // long before, we consider the number with the country calling code
  2799. // stripped to be a better result and keep that instead.
  2800. if ((![self matchesEntirely:validNumberPattern string:fullNumber] &&
  2801. [self matchesEntirely:validNumberPattern string:potentialNationalNumberStr]) ||
  2802. [self testNumberLength:fullNumber desc:generalDesc] == NBEValidationResultTOO_LONG) {
  2803. (*nationalNumber) = [(*nationalNumber) stringByAppendingString:potentialNationalNumberStr];
  2804. if (keepRawInput) {
  2805. (*phoneNumber).countryCodeSource =
  2806. [NSNumber numberWithInteger:NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN];
  2807. }
  2808. (*phoneNumber).countryCode = defaultCountryCode;
  2809. return defaultCountryCode;
  2810. }
  2811. }
  2812. }
  2813. // No country calling code present.
  2814. (*phoneNumber).countryCode = @0;
  2815. return @0;
  2816. }
  2817. /**
  2818. * Returns true if there is any possible number data set for a particular PhoneNumberDesc.
  2819. */
  2820. - (BOOL)descHasPossibleNumberData:(NBPhoneNumberDesc *)desc {
  2821. // If this is empty, it means numbers of this type inherit from the "general desc" -> the value
  2822. // "-1" means that no numbers exist for this type.
  2823. return [desc.possibleLength count] != 1 ||
  2824. ![[desc.possibleLength firstObject] isEqualToNumber:@(-1)];
  2825. }
  2826. /**
  2827. * Strips the IDD from the start of the number if present. Helper function used
  2828. * by maybeStripInternationalPrefixAndNormalize.
  2829. *
  2830. * - param {!RegExp} iddPattern the regular expression for the international
  2831. * prefix.
  2832. * - param {!goog.string.StringBuffer} number the phone number that we wish to
  2833. * strip any international dialing prefix from.
  2834. * @return {boolean} NO if an international prefix was present.
  2835. * @private
  2836. */
  2837. - (BOOL)parsePrefixAsIdd:(NSString *)iddPattern sourceString:(NSString **)number {
  2838. if (number == NULL) {
  2839. return NO;
  2840. }
  2841. NSString *numberStr = [(*number)copy];
  2842. if ([self stringPositionByRegex:numberStr regex:iddPattern] == 0) {
  2843. NSTextCheckingResult *matched =
  2844. [[self matchesByRegex:numberStr regex:iddPattern] objectAtIndex:0];
  2845. NSString *matchedString = [numberStr substringWithRange:matched.range];
  2846. NSUInteger matchEnd = matchedString.length;
  2847. NSString *remainString = [numberStr substringFromIndex:matchEnd];
  2848. NSArray *matchedGroups =
  2849. [_CAPTURING_DIGIT_PATTERN matchesInString:remainString
  2850. options:0
  2851. range:NSMakeRange(0, remainString.length)];
  2852. if (matchedGroups && [matchedGroups count] > 0 && [matchedGroups objectAtIndex:0] != nil) {
  2853. NSString *digitMatched = [remainString
  2854. substringWithRange:((NSTextCheckingResult *)[matchedGroups objectAtIndex:0]).range];
  2855. if (digitMatched.length > 0) {
  2856. NSString *normalizedGroup = [self normalizeDigitsOnly:digitMatched];
  2857. if ([normalizedGroup isEqualToString:@"0"]) {
  2858. return NO;
  2859. }
  2860. }
  2861. }
  2862. (*number) = [remainString copy];
  2863. return YES;
  2864. }
  2865. return NO;
  2866. }
  2867. /**
  2868. * Strips any international prefix (such as +, 00, 011) present in the number
  2869. * provided, normalizes the resulting number, and indicates if an international
  2870. * prefix was present.
  2871. *
  2872. * - param {!goog.string.StringBuffer} number the non-normalized telephone number
  2873. * that we wish to strip any international dialing prefix from.
  2874. * - param {string} possibleIddPrefix the international direct dialing prefix
  2875. * from the region we think this number may be dialed in.
  2876. * @return {CountryCodeSource} the corresponding
  2877. * CountryCodeSource if an international dialing prefix could be removed
  2878. * from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if
  2879. * the number did not seem to be in international format.
  2880. */
  2881. - (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString **)numberStr
  2882. possibleIddPrefix:(NSString *)possibleIddPrefix {
  2883. if (numberStr == NULL || (*numberStr).length == 0) {
  2884. return NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
  2885. }
  2886. // Check to see if the number begins with one or more plus signs.
  2887. if ([self isStartingStringByRegex:(*numberStr)regex:LEADING_PLUS_CHARS_PATTERN]) {
  2888. (*numberStr) =
  2889. [self replaceStringByRegex:(*numberStr)regex:LEADING_PLUS_CHARS_PATTERN withTemplate:@""];
  2890. // Can now normalize the rest of the number since we've consumed the '+'
  2891. // sign at the start.
  2892. (*numberStr) = [self normalize:(*numberStr)];
  2893. return NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN;
  2894. }
  2895. // Attempt to parse the first digits as an international prefix.
  2896. NSString *iddPattern = [possibleIddPrefix copy];
  2897. [self normalizeSB:numberStr];
  2898. return [self parsePrefixAsIdd:iddPattern sourceString:numberStr]
  2899. ? NBECountryCodeSourceFROM_NUMBER_WITH_IDD
  2900. : NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
  2901. }
  2902. /**
  2903. * Strips any national prefix (such as 0, 1) present in the number provided.
  2904. *
  2905. * - param {!goog.string.StringBuffer} number the normalized telephone number
  2906. * that we wish to strip any national dialing prefix from.
  2907. * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
  2908. * region that we think this number is from.
  2909. * - param {goog.string.StringBuffer} carrierCode a place to insert the carrier
  2910. * code if one is extracted.
  2911. * @return {boolean} NO if a national prefix or carrier code (or both) could
  2912. * be extracted.
  2913. */
  2914. - (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString **)number
  2915. metadata:(NBPhoneMetaData *)metadata
  2916. carrierCode:(NSString **)carrierCode {
  2917. if (number == NULL) {
  2918. return NO;
  2919. }
  2920. NSString *numberStr = [(*number)copy];
  2921. NSUInteger numberLength = numberStr.length;
  2922. NSString *possibleNationalPrefix = metadata.nationalPrefixForParsing;
  2923. if (numberLength == 0 || [NBMetadataHelper hasValue:possibleNationalPrefix] == NO) {
  2924. // Early return for numbers of zero length.
  2925. return NO;
  2926. }
  2927. // Attempt to parse the first digits as a national prefix.
  2928. NSString *prefixPattern = [NSString stringWithFormat:@"^(?:%@)", possibleNationalPrefix];
  2929. NSError *error = nil;
  2930. NSRegularExpression *currentPattern =
  2931. [self regularExpressionWithPattern:prefixPattern options:0 error:&error];
  2932. NSArray *prefixMatcher =
  2933. [currentPattern matchesInString:numberStr options:0 range:NSMakeRange(0, numberLength)];
  2934. if (prefixMatcher && [prefixMatcher count] > 0) {
  2935. NSString *nationalNumberRule = metadata.generalDesc.nationalNumberPattern;
  2936. NSTextCheckingResult *firstMatch = [prefixMatcher objectAtIndex:0];
  2937. NSString *firstMatchString = [numberStr substringWithRange:firstMatch.range];
  2938. // prefixMatcher[numOfGroups] == null implies nothing was captured by the
  2939. // capturing groups in possibleNationalPrefix; therefore, no transformation
  2940. // is necessary, and we just remove the national prefix.
  2941. NSUInteger numOfGroups = firstMatch.numberOfRanges - 1;
  2942. NSString *transformRule = metadata.nationalPrefixTransformRule;
  2943. NSString *transformedNumber = @"";
  2944. NSRange firstRange = [firstMatch rangeAtIndex:numOfGroups];
  2945. NSString *firstMatchStringWithGroup =
  2946. (firstRange.location != NSNotFound && firstRange.location < numberStr.length)
  2947. ? [numberStr substringWithRange:firstRange]
  2948. : nil;
  2949. BOOL noTransform = (transformRule == nil || transformRule.length == 0 ||
  2950. [NBMetadataHelper hasValue:firstMatchStringWithGroup] == NO);
  2951. if (noTransform) {
  2952. transformedNumber = [numberStr substringFromIndex:firstMatchString.length];
  2953. } else {
  2954. transformedNumber =
  2955. [self replaceFirstStringByRegex:numberStr regex:prefixPattern withTemplate:transformRule];
  2956. }
  2957. // If the original number was viable, and the resultant number is not,
  2958. // we return.
  2959. if ([NBMetadataHelper hasValue:nationalNumberRule] &&
  2960. [self matchesEntirely:nationalNumberRule string:numberStr] &&
  2961. [self matchesEntirely:nationalNumberRule string:transformedNumber] == NO) {
  2962. return NO;
  2963. }
  2964. if ((noTransform && numOfGroups > 0 && [NBMetadataHelper hasValue:firstMatchStringWithGroup]) ||
  2965. (!noTransform && numOfGroups > 1)) {
  2966. if (carrierCode != NULL && (*carrierCode) != nil) {
  2967. (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchStringWithGroup];
  2968. }
  2969. } else if ((noTransform && numOfGroups > 0 && [NBMetadataHelper hasValue:firstMatchString]) ||
  2970. (!noTransform && numOfGroups > 1)) {
  2971. if (carrierCode != NULL && (*carrierCode) != nil) {
  2972. (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchString];
  2973. }
  2974. }
  2975. (*number) = transformedNumber;
  2976. return YES;
  2977. }
  2978. return NO;
  2979. }
  2980. /**
  2981. * Strips any extension (as in, the part of the number dialled after the call is
  2982. * connected, usually indicated with extn, ext, x or similar) from the end of
  2983. * the number, and returns it.
  2984. *
  2985. * - param {!goog.string.StringBuffer} number the non-normalized telephone number
  2986. * that we wish to strip the extension from.
  2987. * @return {string} the phone extension.
  2988. */
  2989. - (NSString *)maybeStripExtension:(NSString **)number {
  2990. if (number == NULL) {
  2991. return @"";
  2992. }
  2993. NSString *numberStr = [(*number)copy];
  2994. int mStart = [self stringPositionByRegex:numberStr regex:EXTN_PATTERN];
  2995. // If we find a potential extension, and the number preceding this is a viable
  2996. // number, we assume it is an extension.
  2997. if (mStart >= 0 &&
  2998. [self isViablePhoneNumber:[numberStr substringWithRange:NSMakeRange(0, mStart)]]) {
  2999. // The numbers are captured into groups in the regular expression.
  3000. NSTextCheckingResult *firstMatch = [self matchFirstByRegex:numberStr regex:EXTN_PATTERN];
  3001. NSUInteger matchedGroupsLength = [firstMatch numberOfRanges];
  3002. for (NSUInteger i = 1; i < matchedGroupsLength; i++) {
  3003. NSRange curRange = [firstMatch rangeAtIndex:i];
  3004. if (curRange.location != NSNotFound && curRange.location < numberStr.length) {
  3005. NSString *matchString = [(*number) substringWithRange:curRange];
  3006. // We go through the capturing groups until we find one that captured
  3007. // some digits. If none did, then we will return the empty string.
  3008. NSString *tokenedString = [numberStr substringWithRange:NSMakeRange(0, mStart)];
  3009. (*number) = @"";
  3010. (*number) = [(*number) stringByAppendingString:tokenedString];
  3011. return matchString;
  3012. }
  3013. }
  3014. }
  3015. return @"";
  3016. }
  3017. /**
  3018. * Checks to see that the region code used is valid, or if it is not valid, that
  3019. * the number to parse starts with a + symbol so that we can attempt to infer
  3020. * the region from the number.
  3021. * - param {string} numberToParse number that we are attempting to parse.
  3022. * - param {?string} defaultRegion region that we are expecting the number to be
  3023. * from.
  3024. * @return {boolean} NO if it cannot use the region provided and the region
  3025. * cannot be inferred.
  3026. * @private
  3027. */
  3028. - (BOOL)checkRegionForParsing:(NSString *)numberToParse defaultRegion:(NSString *)defaultRegion {
  3029. // If the number is nil or empty, we can't infer the region.
  3030. return [self isValidRegionCode:defaultRegion] ||
  3031. (numberToParse != nil && numberToParse.length > 0 &&
  3032. [self isStartingStringByRegex:numberToParse regex:LEADING_PLUS_CHARS_PATTERN]);
  3033. }
  3034. /**
  3035. * Parses a string and returns it in proto buffer format. This method will throw
  3036. * a {@link i18n.phonenumbers.Error} if the number is not considered to be a
  3037. * possible number. Note that validation of whether the number is actually a
  3038. * valid number for a particular region is not performed. This can be done
  3039. * separately with {@link #isValidNumber}.
  3040. *
  3041. * - param {?string} numberToParse number that we are attempting to parse. This
  3042. * can contain formatting such as +, ( and -, as well as a phone number
  3043. * extension. It can also be provided in RFC3966 format.
  3044. * - param {?string} defaultRegion region that we are expecting the number to be
  3045. * from. This is only used if the number being parsed is not written in
  3046. * international format. The country_code for the number in this case would
  3047. * be stored as that of the default region supplied. If the number is
  3048. * guaranteed to start with a '+' followed by the country calling code, then
  3049. * 'ZZ' or nil can be supplied.
  3050. * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
  3051. * with the parsed number.
  3052. * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
  3053. * viable phone number or if no default region was supplied and the number
  3054. * is not in international format (does not start with +).
  3055. */
  3056. - (NBPhoneNumber *)parse:(NSString *)numberToParse
  3057. defaultRegion:(NSString *)defaultRegion
  3058. error:(NSError **)error {
  3059. NSError *anError = nil;
  3060. NBPhoneNumber *phoneNumber = [self parseHelper:numberToParse
  3061. defaultRegion:defaultRegion
  3062. keepRawInput:NO
  3063. checkRegion:YES
  3064. error:&anError];
  3065. if (anError != nil) {
  3066. if (error != NULL) {
  3067. (*error) = [self errorWithObject:anError.description withDomain:anError.domain];
  3068. }
  3069. }
  3070. return phoneNumber;
  3071. }
  3072. /**
  3073. * Parses a string using the phone's carrier region (when available, ZZ otherwise).
  3074. * This uses the country the sim card in the phone is registered with.
  3075. * For example if you have an AT&T sim card but are in Europe, this will parse the
  3076. * number using +1 (AT&T is a US Carrier) as the default country code.
  3077. * This also works for CDMA phones which don't have a sim card.
  3078. */
  3079. - (NBPhoneNumber *)parseWithPhoneCarrierRegion:(NSString *)numberToParse error:(NSError **)error {
  3080. numberToParse = NormalizeNonBreakingSpace(numberToParse);
  3081. NSString *defaultRegion = nil;
  3082. #if TARGET_OS_IOS
  3083. defaultRegion = [self countryCodeByCarrier];
  3084. #else
  3085. defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
  3086. #endif
  3087. if ([NB_UNKNOWN_REGION isEqualToString:defaultRegion]) {
  3088. // get region from device as a failover (e.g. iPad)
  3089. NSLocale *currentLocale = [NSLocale currentLocale];
  3090. defaultRegion = [currentLocale objectForKey:NSLocaleCountryCode];
  3091. }
  3092. return [self parse:numberToParse defaultRegion:defaultRegion error:error];
  3093. }
  3094. #if TARGET_OS_IOS
  3095. static CTTelephonyNetworkInfo *_telephonyNetworkInfo;
  3096. - (CTTelephonyNetworkInfo *)telephonyNetworkInfo {
  3097. // cache telephony network info;
  3098. // CTTelephonyNetworkInfo objects are unnecessarily created for every call to
  3099. // parseWithPhoneCarrierRegion:error: when in reality this information not change while an app
  3100. // lives in memory real-world performance test while parsing 93 phone numbers: before change:
  3101. // 126ms after change: 32ms using static instance prevents deallocation crashes due to ios bug
  3102. static dispatch_once_t onceToken;
  3103. dispatch_once(&onceToken, ^{
  3104. _telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init];
  3105. });
  3106. return _telephonyNetworkInfo;
  3107. }
  3108. - (NSString *)countryCodeByCarrier {
  3109. NSString *isoCode = [[self.telephonyNetworkInfo subscriberCellularProvider] isoCountryCode];
  3110. // The 2nd part of the if is working around an iOS 7 bug
  3111. // If the SIM card is missing, iOS 7 returns an empty string instead of nil
  3112. if (isoCode.length == 0) {
  3113. isoCode = NB_UNKNOWN_REGION;
  3114. }
  3115. return isoCode;
  3116. }
  3117. #endif
  3118. /**
  3119. * Parses a string and returns it in proto buffer format. This method differs
  3120. * from {@link #parse} in that it always populates the raw_input field of the
  3121. * protocol buffer with numberToParse as well as the country_code_source field.
  3122. *
  3123. * - param {string} numberToParse number that we are attempting to parse. This
  3124. * can contain formatting such as +, ( and -, as well as a phone number
  3125. * extension.
  3126. * - param {?string} defaultRegion region that we are expecting the number to be
  3127. * from. This is only used if the number being parsed is not written in
  3128. * international format. The country calling code for the number in this
  3129. * case would be stored as that of the default region supplied.
  3130. * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
  3131. * with the parsed number.
  3132. * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
  3133. * viable phone number or if no default region was supplied.
  3134. */
  3135. - (NBPhoneNumber *)parseAndKeepRawInput:(NSString *)numberToParse
  3136. defaultRegion:(NSString *)defaultRegion
  3137. error:(NSError **)error {
  3138. if ([self isValidRegionCode:defaultRegion] == NO) {
  3139. if (numberToParse.length > 0 && [numberToParse hasPrefix:@"+"] == NO) {
  3140. NSDictionary *userInfo = [NSDictionary
  3141. dictionaryWithObject:[NSString stringWithFormat:@"Invalid country code:%@", numberToParse]
  3142. forKey:NSLocalizedDescriptionKey];
  3143. if (error != NULL) {
  3144. (*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
  3145. }
  3146. }
  3147. }
  3148. return [self parseHelper:numberToParse
  3149. defaultRegion:defaultRegion
  3150. keepRawInput:YES
  3151. checkRegion:YES
  3152. error:error];
  3153. }
  3154. /**
  3155. * A helper function to set the values related to leading zeros in a
  3156. * PhoneNumber.
  3157. *
  3158. * - param {string} nationalNumber the number we are parsing.
  3159. * - param {i18n.phonenumbers.PhoneNumber} phoneNumber a phone number proto
  3160. * buffer to fill in.
  3161. * @private
  3162. */
  3163. - (void)setItalianLeadingZerosForPhoneNumber:(NSString *)nationalNumber
  3164. phoneNumber:(NBPhoneNumber *)phoneNumber {
  3165. if (nationalNumber.length > 1 && [nationalNumber hasPrefix:@"0"]) {
  3166. phoneNumber.italianLeadingZero = YES;
  3167. NSInteger numberOfLeadingZeros = 1;
  3168. // Note that if the national number is all "0"s, the last "0" is not counted
  3169. // as a leading zero.
  3170. while (numberOfLeadingZeros < nationalNumber.length - 1 &&
  3171. [[nationalNumber substringWithRange:NSMakeRange(numberOfLeadingZeros, 1)]
  3172. isEqualToString:@"0"]) {
  3173. numberOfLeadingZeros++;
  3174. }
  3175. if (numberOfLeadingZeros != 1) {
  3176. phoneNumber.numberOfLeadingZeros = @(numberOfLeadingZeros);
  3177. }
  3178. }
  3179. }
  3180. /**
  3181. * Parses a string and returns it in proto buffer format. This method is the
  3182. * same as the public {@link #parse} method, with the exception that it allows
  3183. * the default region to be nil, for use by {@link #isNumberMatch}.
  3184. *
  3185. * - param {?string} numberToParse number that we are attempting to parse. This
  3186. * can contain formatting such as +, ( and -, as well as a phone number
  3187. * extension.
  3188. * - param {?string} defaultRegion region that we are expecting the number to be
  3189. * from. This is only used if the number being parsed is not written in
  3190. * international format. The country calling code for the number in this
  3191. * case would be stored as that of the default region supplied.
  3192. * - param {boolean} keepRawInput whether to populate the raw_input field of the
  3193. * phoneNumber with numberToParse.
  3194. * - param {boolean} checkRegion should be set to NO if it is permitted for
  3195. * the default coregion to be nil or unknown ('ZZ').
  3196. * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
  3197. * with the parsed number.
  3198. * @throws {i18n.phonenumbers.Error}
  3199. * @private
  3200. */
  3201. - (NBPhoneNumber *)parseHelper:(NSString *)numberToParse
  3202. defaultRegion:(NSString *)defaultRegion
  3203. keepRawInput:(BOOL)keepRawInput
  3204. checkRegion:(BOOL)checkRegion
  3205. error:(NSError **)error {
  3206. numberToParse = NormalizeNonBreakingSpace(numberToParse);
  3207. if (numberToParse == nil) {
  3208. if (error != NULL) {
  3209. (*error) = [self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", numberToParse]
  3210. withDomain:@"NOT_A_NUMBER"];
  3211. }
  3212. return nil;
  3213. } else if (numberToParse.length > MAX_INPUT_STRING_LENGTH_) {
  3214. if (error != NULL) {
  3215. (*error) = [self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", numberToParse]
  3216. withDomain:@"TOO_LONG"];
  3217. }
  3218. return nil;
  3219. }
  3220. NSString *nationalNumber = @"";
  3221. [self buildNationalNumberForParsing:numberToParse nationalNumber:&nationalNumber];
  3222. if ([self isViablePhoneNumber:nationalNumber] == NO) {
  3223. if (error != NULL) {
  3224. (*error) =
  3225. [self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", nationalNumber]
  3226. withDomain:@"NOT_A_NUMBER"];
  3227. }
  3228. return nil;
  3229. }
  3230. // Check the region supplied is valid, or that the extracted number starts
  3231. // with some sort of + sign so the number's region can be determined.
  3232. if (checkRegion &&
  3233. [self checkRegionForParsing:nationalNumber defaultRegion:defaultRegion] == NO) {
  3234. if (error != NULL) {
  3235. (*error) = [self
  3236. errorWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", defaultRegion]
  3237. withDomain:@"INVALID_COUNTRY_CODE"];
  3238. }
  3239. return nil;
  3240. }
  3241. NBPhoneNumber *phoneNumber = [[NBPhoneNumber alloc] init];
  3242. if (keepRawInput) {
  3243. phoneNumber.rawInput = [numberToParse copy];
  3244. }
  3245. // Attempt to parse extension first, since it doesn't require region-specific
  3246. // data and we want to have the non-normalised number here.
  3247. NSString *extension = [self maybeStripExtension:&nationalNumber];
  3248. if (extension.length > 0) {
  3249. phoneNumber.extension = [extension copy];
  3250. }
  3251. NBPhoneMetaData *regionMetadata = [self.helper getMetadataForRegion:defaultRegion];
  3252. // Check to see if the number is given in international format so we know
  3253. // whether this number is from the default region or not.
  3254. NSString *normalizedNationalNumber = @"";
  3255. NSNumber *countryCode = nil;
  3256. NSString *nationalNumberStr = [nationalNumber copy];
  3257. {
  3258. NSError *anError = nil;
  3259. countryCode = [self maybeExtractCountryCode:nationalNumberStr
  3260. metadata:regionMetadata
  3261. nationalNumber:&normalizedNationalNumber
  3262. keepRawInput:keepRawInput
  3263. phoneNumber:&phoneNumber
  3264. error:&anError];
  3265. if (anError != nil) {
  3266. if ([anError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] &&
  3267. [self stringPositionByRegex:nationalNumberStr regex:LEADING_PLUS_CHARS_PATTERN] >= 0) {
  3268. // Strip the plus-char, and try again.
  3269. NSError *aNestedError = nil;
  3270. nationalNumberStr = [self replaceStringByRegex:nationalNumberStr
  3271. regex:LEADING_PLUS_CHARS_PATTERN
  3272. withTemplate:@""];
  3273. countryCode = [self maybeExtractCountryCode:nationalNumberStr
  3274. metadata:regionMetadata
  3275. nationalNumber:&normalizedNationalNumber
  3276. keepRawInput:keepRawInput
  3277. phoneNumber:&phoneNumber
  3278. error:&aNestedError];
  3279. if ([countryCode isEqualToNumber:@0]) {
  3280. if (error != NULL)
  3281. (*error) = [self errorWithObject:anError.description withDomain:anError.domain];
  3282. return nil;
  3283. }
  3284. } else {
  3285. if (error != NULL)
  3286. (*error) = [self errorWithObject:anError.description withDomain:anError.domain];
  3287. return nil;
  3288. }
  3289. }
  3290. }
  3291. if (![countryCode isEqualToNumber:@0]) {
  3292. NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCode];
  3293. if (phoneNumberRegion != defaultRegion) {
  3294. // Metadata cannot be nil because the country calling code is valid.
  3295. regionMetadata =
  3296. [self getMetadataForRegionOrCallingCode:countryCode regionCode:phoneNumberRegion];
  3297. }
  3298. } else {
  3299. // If no extracted country calling code, use the region supplied instead.
  3300. // The national number is just the normalized version of the number we were
  3301. // given to parse.
  3302. [self normalizeSB:&nationalNumber];
  3303. normalizedNationalNumber = [normalizedNationalNumber stringByAppendingString:nationalNumber];
  3304. if (defaultRegion != nil) {
  3305. countryCode = regionMetadata.countryCode;
  3306. phoneNumber.countryCode = countryCode;
  3307. } else if (keepRawInput) {
  3308. [phoneNumber clearCountryCodeSource];
  3309. }
  3310. }
  3311. if (normalizedNationalNumber.length < MIN_LENGTH_FOR_NSN_) {
  3312. if (error != NULL) {
  3313. (*error) = [self
  3314. errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
  3315. withDomain:@"TOO_SHORT_NSN"];
  3316. }
  3317. return nil;
  3318. }
  3319. if (regionMetadata != nil) {
  3320. NSString *carrierCode = @"";
  3321. NSString *potentialNationalNumber = [normalizedNationalNumber copy];
  3322. [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber
  3323. metadata:regionMetadata
  3324. carrierCode:&carrierCode];
  3325. NBEValidationResult validationResult =
  3326. [self validateNumberLength:potentialNationalNumber metadata:regionMetadata];
  3327. if (validationResult != NBEValidationResultTOO_SHORT &&
  3328. validationResult != NBEValidationResultIS_POSSIBLE_LOCAL_ONLY &&
  3329. validationResult != NBEValidationResultINVALID_LENGTH) {
  3330. normalizedNationalNumber = potentialNationalNumber;
  3331. if (keepRawInput) {
  3332. phoneNumber.preferredDomesticCarrierCode = [carrierCode copy];
  3333. }
  3334. }
  3335. }
  3336. NSString *normalizedNationalNumberStr = [normalizedNationalNumber copy];
  3337. NSUInteger lengthOfNationalNumber = normalizedNationalNumberStr.length;
  3338. if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_) {
  3339. if (error != NULL) {
  3340. (*error) = [self
  3341. errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
  3342. withDomain:@"TOO_SHORT_NSN"];
  3343. }
  3344. return nil;
  3345. }
  3346. if (lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_) {
  3347. if (error != NULL) {
  3348. (*error) =
  3349. [self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", normalizedNationalNumber]
  3350. withDomain:@"TOO_LONG"];
  3351. }
  3352. return nil;
  3353. }
  3354. [self setItalianLeadingZerosForPhoneNumber:normalizedNationalNumberStr phoneNumber:phoneNumber];
  3355. phoneNumber.nationalNumber =
  3356. [NSNumber numberWithLongLong:[normalizedNationalNumberStr longLongValue]];
  3357. return phoneNumber;
  3358. }
  3359. /**
  3360. * Converts numberToParse to a form that we can parse and write it to
  3361. * nationalNumber if it is written in RFC3966; otherwise extract a possible
  3362. * number out of it and write to nationalNumber.
  3363. *
  3364. * - param {?string} numberToParse number that we are attempting to parse. This
  3365. * can contain formatting such as +, ( and -, as well as a phone number
  3366. * extension.
  3367. * - param {!goog.string.StringBuffer} nationalNumber a string buffer for storing
  3368. * the national significant number.
  3369. * @private
  3370. */
  3371. - (void)buildNationalNumberForParsing:(NSString *)numberToParse
  3372. nationalNumber:(NSString **)nationalNumber {
  3373. if (nationalNumber == NULL) return;
  3374. NSMutableString *result = [[NSMutableString alloc] init];
  3375. int indexOfPhoneContext = [self indexOfStringByString:numberToParse target:RFC3966_PHONE_CONTEXT];
  3376. if (indexOfPhoneContext > 0) {
  3377. NSUInteger phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT.length;
  3378. // If the phone context contains a phone number prefix, we need to capture
  3379. // it, whereas domains will be ignored.
  3380. if ([numberToParse characterAtIndex:phoneContextStart] == '+') {
  3381. // Additional parameters might follow the phone context. If so, we will
  3382. // remove them here because the parameters after phone context are not
  3383. // important for parsing the phone number.
  3384. NSRange foundRange = [numberToParse
  3385. rangeOfString:@";"
  3386. options:NSLiteralSearch
  3387. range:NSMakeRange(phoneContextStart, numberToParse.length - phoneContextStart)];
  3388. if (foundRange.location != NSNotFound) {
  3389. NSRange subRange = NSMakeRange(phoneContextStart, foundRange.location - phoneContextStart);
  3390. [result appendString:[numberToParse substringWithRange:subRange]];
  3391. } else {
  3392. [result appendString:[numberToParse substringFromIndex:phoneContextStart]];
  3393. }
  3394. }
  3395. // Now append everything between the "tel:" prefix and the phone-context.
  3396. // This should include the national number, an optional extension or
  3397. // isdn-subaddress component.
  3398. NSUInteger rfc3966Start =
  3399. [self indexOfStringByString:numberToParse target:RFC3966_PREFIX] + RFC3966_PREFIX.length;
  3400. NSString *subString = [numberToParse
  3401. substringWithRange:NSMakeRange(rfc3966Start, indexOfPhoneContext - rfc3966Start)];
  3402. [result appendString:subString];
  3403. } else {
  3404. // Extract a possible number from the string passed in (this strips leading
  3405. // characters that could not be the start of a phone number.)
  3406. [result appendString:[self extractPossibleNumber:numberToParse]];
  3407. }
  3408. // Delete the isdn-subaddress and everything after it if it is present.
  3409. // Note extension won't appear at the same time with isdn-subaddress
  3410. // according to paragraph 5.3 of the RFC3966 spec,
  3411. NSString *nationalNumberStr = [result copy];
  3412. int indexOfIsdn = [self indexOfStringByString:nationalNumberStr target:RFC3966_ISDN_SUBADDRESS];
  3413. if (indexOfIsdn > 0) {
  3414. NSRange range = NSMakeRange(0, indexOfIsdn);
  3415. result = [[NSMutableString alloc] initWithString:[nationalNumberStr substringWithRange:range]];
  3416. }
  3417. // If both phone context and isdn-subaddress are absent but other
  3418. // parameters are present, the parameters are left in nationalNumber. This
  3419. // is because we are concerned about deleting content from a potential
  3420. // number string when there is no strong evidence that the number is
  3421. // actually written in RFC3966.
  3422. *nationalNumber = [result copy];
  3423. }
  3424. /**
  3425. * Takes two phone numbers and compares them for equality.
  3426. *
  3427. * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
  3428. * for Italian numbers and any extension present are the same. Returns NSN_MATCH
  3429. * if either or both has no region specified, and the NSNs and extensions are
  3430. * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
  3431. * or the region specified is the same, and one NSN could be a shorter version
  3432. * of the other number. This includes the case where one has an extension
  3433. * specified, and the other does not. Returns NO_MATCH otherwise. For example,
  3434. * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
  3435. * +1 345 657 1234 and 345 657 are a NO_MATCH.
  3436. *
  3437. * - param {i18n.phonenumbers.PhoneNumber|string} firstNumberIn first number to
  3438. * compare. If it is a string it can contain formatting, and can have
  3439. * country calling code specified with + at the start.
  3440. * - param {i18n.phonenumbers.PhoneNumber|string} secondNumberIn second number to
  3441. * compare. If it is a string it can contain formatting, and can have
  3442. * country calling code specified with + at the start.
  3443. * @return {MatchType} NOT_A_NUMBER, NO_MATCH,
  3444. * SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of
  3445. * equality of the two numbers, described in the method definition.
  3446. */
  3447. - (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn error:(NSError **)error {
  3448. NBEMatchType res = 0;
  3449. @try {
  3450. res = [self isNumberMatch:firstNumberIn second:secondNumberIn];
  3451. } @catch (NSException *exception) {
  3452. NSDictionary *userInfo =
  3453. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  3454. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  3455. }
  3456. return res;
  3457. }
  3458. - (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn {
  3459. // If the input arguements are strings parse them to a proto buffer format.
  3460. // Else make copies of the phone numbers so that the numbers passed in are not
  3461. // edited.
  3462. /** @type {i18n.phonenumbers.PhoneNumber} */
  3463. NBPhoneNumber *firstNumber = nil, *secondNumber = nil;
  3464. if ([firstNumberIn isKindOfClass:[NSString class]]) {
  3465. // First see if the first number has an implicit country calling code, by
  3466. // attempting to parse it.
  3467. NSError *anError;
  3468. firstNumber = [self parse:firstNumberIn defaultRegion:NB_UNKNOWN_REGION error:&anError];
  3469. if (anError != nil) {
  3470. if ([anError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) {
  3471. return NBEMatchTypeNOT_A_NUMBER;
  3472. }
  3473. // The first number has no country calling code. EXACT_MATCH is no longer
  3474. // possible. We parse it as if the region was the same as that for the
  3475. // second number, and if EXACT_MATCH is returned, we replace this with
  3476. // NSN_MATCH.
  3477. if ([secondNumberIn isKindOfClass:[NSString class]] == NO) {
  3478. NSString *secondNumberRegion =
  3479. [self getRegionCodeForCountryCode:((NBPhoneNumber *)secondNumberIn).countryCode];
  3480. if (secondNumberRegion != NB_UNKNOWN_REGION) {
  3481. NSError *aNestedError;
  3482. firstNumber =
  3483. [self parse:firstNumberIn defaultRegion:secondNumberRegion error:&aNestedError];
  3484. if (aNestedError != nil) {
  3485. return NBEMatchTypeNOT_A_NUMBER;
  3486. }
  3487. NBEMatchType match = [self isNumberMatch:firstNumber second:secondNumberIn];
  3488. if (match == NBEMatchTypeEXACT_MATCH) {
  3489. return NBEMatchTypeNSN_MATCH;
  3490. }
  3491. return match;
  3492. }
  3493. }
  3494. // If the second number is a string or doesn't have a valid country
  3495. // calling code, we parse the first number without country calling code.
  3496. NSError *aNestedError;
  3497. firstNumber = [self parseHelper:firstNumberIn
  3498. defaultRegion:nil
  3499. keepRawInput:NO
  3500. checkRegion:NO
  3501. error:&aNestedError];
  3502. if (aNestedError != nil) {
  3503. return NBEMatchTypeNOT_A_NUMBER;
  3504. }
  3505. }
  3506. } else {
  3507. firstNumber = [firstNumberIn copy];
  3508. }
  3509. if ([secondNumberIn isKindOfClass:[NSString class]]) {
  3510. NSError *parseError;
  3511. secondNumber = [self parse:secondNumberIn defaultRegion:NB_UNKNOWN_REGION error:&parseError];
  3512. if (parseError != nil) {
  3513. if ([parseError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) {
  3514. return NBEMatchTypeNOT_A_NUMBER;
  3515. }
  3516. return [self isNumberMatch:secondNumberIn second:firstNumber];
  3517. } else {
  3518. return [self isNumberMatch:firstNumberIn second:secondNumber];
  3519. }
  3520. } else {
  3521. secondNumber = [secondNumberIn copy];
  3522. }
  3523. // First clear raw_input, country_code_source and
  3524. // preferred_domestic_carrier_code fields and any empty-string extensions so
  3525. // that we can use the proto-buffer equality method.
  3526. firstNumber.rawInput = @"";
  3527. [firstNumber clearCountryCodeSource];
  3528. firstNumber.preferredDomesticCarrierCode = @"";
  3529. secondNumber.rawInput = @"";
  3530. [secondNumber clearCountryCodeSource];
  3531. secondNumber.preferredDomesticCarrierCode = @"";
  3532. if (firstNumber.extension != nil && firstNumber.extension.length == 0) {
  3533. firstNumber.extension = nil;
  3534. }
  3535. if (secondNumber.extension != nil && secondNumber.extension.length == 0) {
  3536. secondNumber.extension = nil;
  3537. }
  3538. // Early exit if both had extensions and these are different.
  3539. if ([NBMetadataHelper hasValue:firstNumber.extension] &&
  3540. [NBMetadataHelper hasValue:secondNumber.extension] &&
  3541. [firstNumber.extension isEqualToString:secondNumber.extension] == NO) {
  3542. return NBEMatchTypeNO_MATCH;
  3543. }
  3544. NSNumber *firstNumberCountryCode = firstNumber.countryCode;
  3545. NSNumber *secondNumberCountryCode = secondNumber.countryCode;
  3546. // Both had country_code specified.
  3547. if (![firstNumberCountryCode isEqualToNumber:@0] &&
  3548. ![secondNumberCountryCode isEqualToNumber:@0]) {
  3549. if ([firstNumber isEqual:secondNumber]) {
  3550. return NBEMatchTypeEXACT_MATCH;
  3551. } else if ([firstNumberCountryCode isEqualToNumber:secondNumberCountryCode] &&
  3552. [self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) {
  3553. // A SHORT_NSN_MATCH occurs if there is a difference because of the
  3554. // presence or absence of an 'Italian leading zero', the presence or
  3555. // absence of an extension, or one NSN being a shorter variant of the
  3556. // other.
  3557. return NBEMatchTypeSHORT_NSN_MATCH;
  3558. }
  3559. // This is not a match.
  3560. return NBEMatchTypeNO_MATCH;
  3561. }
  3562. // Checks cases where one or both country_code fields were not specified. To
  3563. // make equality checks easier, we first set the country_code fields to be
  3564. // equal.
  3565. firstNumber.countryCode = @0;
  3566. secondNumber.countryCode = @0;
  3567. // If all else was the same, then this is an NSN_MATCH.
  3568. if ([firstNumber isEqual:secondNumber]) {
  3569. return NBEMatchTypeNSN_MATCH;
  3570. }
  3571. if ([self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) {
  3572. return NBEMatchTypeSHORT_NSN_MATCH;
  3573. }
  3574. return NBEMatchTypeNO_MATCH;
  3575. }
  3576. /**
  3577. * Returns NO when one national number is the suffix of the other or both are
  3578. * the same.
  3579. *
  3580. * - param {i18n.phonenumbers.PhoneNumber} firstNumber the first PhoneNumber
  3581. * object.
  3582. * - param {i18n.phonenumbers.PhoneNumber} secondNumber the second PhoneNumber
  3583. * object.
  3584. * @return {boolean} NO if one PhoneNumber is the suffix of the other one.
  3585. * @private
  3586. */
  3587. - (BOOL)isNationalNumberSuffixOfTheOther:(NBPhoneNumber *)firstNumber
  3588. second:(NBPhoneNumber *)secondNumber {
  3589. NSString *firstNumberNationalNumber =
  3590. [NSString stringWithFormat:@"%@", firstNumber.nationalNumber];
  3591. NSString *secondNumberNationalNumber =
  3592. [NSString stringWithFormat:@"%@", secondNumber.nationalNumber];
  3593. // Note that endsWith returns NO if the numbers are equal.
  3594. return [firstNumberNationalNumber hasSuffix:secondNumberNationalNumber] ||
  3595. [secondNumberNationalNumber hasSuffix:firstNumberNationalNumber];
  3596. }
  3597. /**
  3598. * Returns NO if the number can be dialled from outside the region, or
  3599. * unknown. If the number can only be dialled from within the region, returns
  3600. * NO. Does not check the number is a valid number.
  3601. * TODO: Make this method public when we have enough metadata to make it
  3602. * worthwhile. Currently visible for testing purposes only.
  3603. *
  3604. * - param {i18n.phonenumbers.PhoneNumber} number the phone-number for which we
  3605. * want to know whether it is diallable from outside the region.
  3606. * @return {boolean} NO if the number can only be dialled from within the
  3607. * country.
  3608. */
  3609. - (BOOL)canBeInternationallyDialled:(NBPhoneNumber *)number error:(NSError **)error {
  3610. BOOL res = NO;
  3611. @try {
  3612. res = [self canBeInternationallyDialled:number];
  3613. } @catch (NSException *exception) {
  3614. NSDictionary *userInfo =
  3615. [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
  3616. if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  3617. }
  3618. return res;
  3619. }
  3620. - (BOOL)canBeInternationallyDialled:(NBPhoneNumber *)number {
  3621. NBPhoneMetaData *metadata =
  3622. [self.helper getMetadataForRegion:[self getRegionCodeForNumber:number]];
  3623. if (metadata == nil) {
  3624. // Note numbers belonging to non-geographical entities (e.g. +800 numbers)
  3625. // are always internationally diallable, and will be caught here.
  3626. return YES;
  3627. }
  3628. NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  3629. return [self isNumberMatchingDesc:nationalSignificantNumber
  3630. numberDesc:metadata.noInternationalDialling] == NO;
  3631. }
  3632. /**
  3633. * Check whether the entire input sequence can be matched against the regular
  3634. * expression.
  3635. *
  3636. * - param {!RegExp|string} regex the regular expression to match against.
  3637. * - param {string} str the string to test.
  3638. * @return {boolean} NO if str can be matched entirely against regex.
  3639. * @private
  3640. */
  3641. - (BOOL)matchesEntirely:(NSString *)regex string:(NSString *)str {
  3642. if ([regex isEqualToString:@"NA"]) {
  3643. return NO;
  3644. }
  3645. NSError *error = nil;
  3646. NSRegularExpression *currentPattern =
  3647. [self entireRegularExpressionWithPattern:regex options:0 error:&error];
  3648. NSRange stringRange = NSMakeRange(0, str.length);
  3649. NSTextCheckingResult *matchResult =
  3650. [currentPattern firstMatchInString:str options:NSMatchingAnchored range:stringRange];
  3651. if (matchResult != nil) {
  3652. BOOL matchIsEntireString = NSEqualRanges(matchResult.range, stringRange);
  3653. if (matchIsEntireString) {
  3654. return YES;
  3655. }
  3656. }
  3657. return NO;
  3658. }
  3659. @end