NBMetadataHelper.m 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. //
  2. // NBMetadataHelper.m
  3. // libPhoneNumber
  4. //
  5. // Created by tabby on 2015. 2. 8..
  6. // Copyright (c) 2015년 ohtalk.me. All rights reserved.
  7. //
  8. #import "NBMetadataHelper.h"
  9. #import "NBGeneratedPhoneNumberMetaData.h"
  10. #import "NBPhoneMetaData.h"
  11. @interface NBMetadataHelper ()
  12. // Cached metadata
  13. @property (nonatomic, strong) NSCache<NSString *, NBPhoneMetaData *> *metadataCache;
  14. #if SHORT_NUMBER_SUPPORT
  15. @property (nonatomic, strong) NSCache<NSString *, NBPhoneMetaData *> *shortNumberMetadataCache;
  16. #endif //SHORT_NUMBER_SUPPORT
  17. @end
  18. static NSString *StringByTrimming(NSString *aString) {
  19. static dispatch_once_t onceToken;
  20. static NSCharacterSet *whitespaceCharSet = nil;
  21. dispatch_once(&onceToken, ^{
  22. NSMutableCharacterSet *spaceCharSet =
  23. [NSMutableCharacterSet characterSetWithCharactersInString:NB_NON_BREAKING_SPACE];
  24. [spaceCharSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  25. whitespaceCharSet = spaceCharSet;
  26. });
  27. return [aString stringByTrimmingCharactersInSet:whitespaceCharSet];
  28. }
  29. @implementation NBMetadataHelper
  30. - (instancetype)init {
  31. self = [super init];
  32. if (self != nil) {
  33. _metadataCache = [[NSCache alloc] init];
  34. #if SHORT_NUMBER_SUPPORT
  35. _shortNumberMetadataCache = [[NSCache alloc] init];
  36. #endif //SHORT_NUMBER_SUPPORT
  37. }
  38. return self;
  39. }
  40. /*
  41. Terminologies
  42. - Country Number (CN) = Country code for i18n calling
  43. - Country Code (CC) : ISO country codes (2 chars)
  44. Ref. site (countrycode.org)
  45. */
  46. + (NSDictionary *)phoneNumberDataMap {
  47. static NSDictionary *phoneNumberDataDictionary;
  48. static dispatch_once_t onceToken;
  49. dispatch_once(&onceToken, ^{
  50. phoneNumberDataDictionary =
  51. [self jsonObjectFromZippedDataWithBytes:kPhoneNumberMetaData
  52. compressedLength:kPhoneNumberMetaDataCompressedLength
  53. expandedLength:kPhoneNumberMetaDataExpandedLength];
  54. });
  55. return phoneNumberDataDictionary;
  56. }
  57. + (NSDictionary *)CCode2CNMap {
  58. static NSMutableDictionary *mapCCode2CN;
  59. static dispatch_once_t onceToken;
  60. dispatch_once(&onceToken, ^{
  61. NSDictionary *countryCodeToRegionCodeMap = [self CN2CCodeMap];
  62. mapCCode2CN = [[NSMutableDictionary alloc] init];
  63. for (NSString *countryCode in countryCodeToRegionCodeMap) {
  64. NSArray *regionCodes = countryCodeToRegionCodeMap[countryCode];
  65. for (NSString *regionCode in regionCodes) {
  66. mapCCode2CN[regionCode] = countryCode;
  67. }
  68. }
  69. });
  70. return mapCCode2CN;
  71. }
  72. + (NSDictionary *)CN2CCodeMap {
  73. return [self phoneNumberDataMap][@"countryCodeToRegionCodeMap"];
  74. }
  75. - (NSArray *)getAllMetadata {
  76. NSArray *countryCodes = [NSLocale ISOCountryCodes];
  77. NSMutableArray *resultMetadata = [[NSMutableArray alloc] initWithCapacity:countryCodes.count];
  78. for (NSString *countryCode in countryCodes) {
  79. id countryDictionaryInstance =
  80. [NSDictionary dictionaryWithObject:countryCode forKey:NSLocaleCountryCode];
  81. NSString *identifier = [NSLocale localeIdentifierFromComponents:countryDictionaryInstance];
  82. NSString *country =
  83. [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:identifier];
  84. NSMutableDictionary *countryMeta = [[NSMutableDictionary alloc] init];
  85. if (country) {
  86. [countryMeta setObject:country forKey:@"name"];
  87. } else {
  88. NSString *systemCountry =
  89. [[NSLocale systemLocale] displayNameForKey:NSLocaleIdentifier value:identifier];
  90. if (systemCountry) {
  91. [countryMeta setObject:systemCountry forKey:@"name"];
  92. }
  93. }
  94. if (countryCode) {
  95. [countryMeta setObject:countryCode forKey:@"code"];
  96. }
  97. NBPhoneMetaData *metaData = [self getMetadataForRegion:countryCode];
  98. if (metaData) {
  99. [countryMeta setObject:metaData forKey:@"metadata"];
  100. }
  101. [resultMetadata addObject:countryMeta];
  102. }
  103. return resultMetadata;
  104. }
  105. + (NSArray *)regionCodeFromCountryCode:(NSNumber *)countryCodeNumber {
  106. NSArray *res = [self CN2CCodeMap][[countryCodeNumber stringValue]];
  107. if ([res isKindOfClass:[NSArray class]] && [res count] > 0) {
  108. return res;
  109. }
  110. return nil;
  111. }
  112. + (NSString *)countryCodeFromRegionCode:(NSString *)regionCode {
  113. return [self CCode2CNMap][regionCode];
  114. }
  115. /**
  116. * Returns the metadata for the given region code or {@code nil} if the region
  117. * code is invalid or unknown.
  118. *
  119. * @param {?string} regionCode
  120. * @return {i18n.phonenumbers.PhoneMetadata}
  121. */
  122. - (NBPhoneMetaData *)getMetadataForRegion:(NSString *)regionCode {
  123. regionCode = StringByTrimming(regionCode);
  124. if (regionCode.length == 0) {
  125. return nil;
  126. }
  127. regionCode = [regionCode uppercaseString];
  128. NBPhoneMetaData *cachedMetadata = [_metadataCache objectForKey:regionCode];
  129. if (cachedMetadata != nil) {
  130. return cachedMetadata;
  131. }
  132. NSDictionary *dict = [[self class] phoneNumberDataMap][@"countryToMetadata"];
  133. NSArray *entry = dict[regionCode];
  134. if (entry) {
  135. NBPhoneMetaData *metadata = [[NBPhoneMetaData alloc] initWithEntry:entry];
  136. [_metadataCache setObject:metadata forKey:regionCode];
  137. return metadata;
  138. }
  139. return nil;
  140. }
  141. /**
  142. * @param countryCallingCode countryCallingCode
  143. * @return {i18n.phonenumbers.PhoneMetadata}
  144. */
  145. - (NBPhoneMetaData *)getMetadataForNonGeographicalRegion:(NSNumber *)countryCallingCode {
  146. NSString *countryCallingCodeStr = countryCallingCode.stringValue;
  147. return [self getMetadataForRegion:countryCallingCodeStr];
  148. }
  149. + (BOOL)hasValue:(NSString *)string {
  150. string = StringByTrimming(string);
  151. return string.length != 0;
  152. }
  153. #if SHORT_NUMBER_SUPPORT
  154. + (NSDictionary *)shortNumberDataMap {
  155. static NSDictionary *shortNumberDataDictionary;
  156. static dispatch_once_t onceToken;
  157. dispatch_once(&onceToken, ^{
  158. shortNumberDataDictionary =
  159. [self jsonObjectFromZippedDataWithBytes:kShortNumberMetaData
  160. compressedLength:kShortNumberMetaDataCompressedLength
  161. expandedLength:kShortNumberMetaDataExpandedLength];
  162. });
  163. return shortNumberDataDictionary;
  164. }
  165. - (NBPhoneMetaData *)shortNumberMetadataForRegion:(NSString *)regionCode
  166. {
  167. regionCode = StringByTrimming(regionCode);
  168. if (regionCode.length == 0) {
  169. return nil;
  170. }
  171. regionCode = [regionCode uppercaseString];
  172. NBPhoneMetaData *cachedMetadata = [_shortNumberMetadataCache objectForKey:regionCode];
  173. if (cachedMetadata != nil) {
  174. return cachedMetadata;
  175. }
  176. NSDictionary *dict = [[self class] shortNumberDataMap][@"countryToMetadata"];
  177. NSArray *entry = dict[regionCode];
  178. if (entry) {
  179. NBPhoneMetaData *metadata = [[NBPhoneMetaData alloc] initWithEntry:entry];
  180. [_shortNumberMetadataCache setObject:metadata forKey:regionCode];
  181. return metadata;
  182. }
  183. return nil;
  184. }
  185. #endif // SHORT_NUMBER_SUPPORT
  186. /**
  187. * Expand gzipped data into a JSON object.
  188. * @param bytes Array<Bytef> of zipped data.
  189. * @param compressedLength Length of the compressed bytes.
  190. * @param expandedLength Length of the expanded bytes.
  191. * @return JSON dictionary.
  192. */
  193. + (NSDictionary *)jsonObjectFromZippedDataWithBytes:(z_const Bytef [])bytes
  194. compressedLength:(NSUInteger)compressedLength
  195. expandedLength:(NSUInteger)expandedLength {
  196. // Data is a gzipped JSON file that is embedded in the binary.
  197. // See GeneratePhoneNumberHeader.sh and PhoneNumberMetaData.h for details.
  198. NSMutableData* gunzippedData = [NSMutableData dataWithLength:expandedLength];
  199. z_stream zStream;
  200. memset(&zStream, 0, sizeof(zStream));
  201. __attribute((unused)) int err = inflateInit2(&zStream, 16);
  202. NSAssert(err == Z_OK, @"Unable to init stream. err = %d", err);
  203. zStream.next_in = bytes;
  204. zStream.avail_in = (uint)compressedLength;
  205. zStream.next_out = (Bytef *)gunzippedData.bytes;
  206. zStream.avail_out = (uint)gunzippedData.length;
  207. err = inflate(&zStream, Z_FINISH);
  208. NSAssert(err == Z_STREAM_END, @"Unable to inflate compressed data. err = %d", err);
  209. err = inflateEnd(&zStream);
  210. NSAssert(err == Z_OK, @"Unable to inflate compressed data. err = %d", err);
  211. NSError *error = nil;
  212. NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:gunzippedData
  213. options:0
  214. error:&error];
  215. NSAssert(error == nil, @"Unable to convert JSON - %@", error);
  216. return jsonObject;
  217. }
  218. @end