libPhoneNumber-iOS/libPhoneNumber/NBMetadataHelper.m

268 lines
8.2 KiB
Objective-C

//
// NBMetadataHelper.m
// libPhoneNumber
//
// Created by tabby on 2015. 2. 8..
// Copyright (c) 2015년 ohtalk.me. All rights reserved.
//
#import "NBMetadataHelper.h"
#import "NBGeneratedPhoneNumberMetaData.h"
#import "NBPhoneMetaData.h"
@interface NBMetadataHelper ()
// Cached metadata
@property (nonatomic, strong) NSCache<NSString *, NBPhoneMetaData *> *metadataCache;
#if SHORT_NUMBER_SUPPORT
@property (nonatomic, strong) NSCache<NSString *, NBPhoneMetaData *> *shortNumberMetadataCache;
#endif //SHORT_NUMBER_SUPPORT
@end
static NSString *StringByTrimming(NSString *aString) {
static dispatch_once_t onceToken;
static NSCharacterSet *whitespaceCharSet = nil;
dispatch_once(&onceToken, ^{
NSMutableCharacterSet *spaceCharSet =
[NSMutableCharacterSet characterSetWithCharactersInString:NB_NON_BREAKING_SPACE];
[spaceCharSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
whitespaceCharSet = spaceCharSet;
});
return [aString stringByTrimmingCharactersInSet:whitespaceCharSet];
}
@implementation NBMetadataHelper
- (instancetype)init {
self = [super init];
if (self != nil) {
_metadataCache = [[NSCache alloc] init];
#if SHORT_NUMBER_SUPPORT
_shortNumberMetadataCache = [[NSCache alloc] init];
#endif //SHORT_NUMBER_SUPPORT
}
return self;
}
/*
Terminologies
- Country Number (CN) = Country code for i18n calling
- Country Code (CC) : ISO country codes (2 chars)
Ref. site (countrycode.org)
*/
+ (NSDictionary *)phoneNumberDataMap {
static NSDictionary *phoneNumberDataDictionary;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
phoneNumberDataDictionary =
[self jsonObjectFromZippedDataWithBytes:kPhoneNumberMetaData
compressedLength:kPhoneNumberMetaDataCompressedLength
expandedLength:kPhoneNumberMetaDataExpandedLength];
});
return phoneNumberDataDictionary;
}
+ (NSDictionary *)CCode2CNMap {
static NSMutableDictionary *mapCCode2CN;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *countryCodeToRegionCodeMap = [self CN2CCodeMap];
mapCCode2CN = [[NSMutableDictionary alloc] init];
for (NSString *countryCode in countryCodeToRegionCodeMap) {
NSArray *regionCodes = countryCodeToRegionCodeMap[countryCode];
for (NSString *regionCode in regionCodes) {
mapCCode2CN[regionCode] = countryCode;
}
}
});
return mapCCode2CN;
}
+ (NSDictionary *)CN2CCodeMap {
return [self phoneNumberDataMap][@"countryCodeToRegionCodeMap"];
}
- (NSArray *)getAllMetadata {
NSArray *countryCodes = [NSLocale ISOCountryCodes];
NSMutableArray *resultMetadata = [[NSMutableArray alloc] initWithCapacity:countryCodes.count];
for (NSString *countryCode in countryCodes) {
id countryDictionaryInstance =
[NSDictionary dictionaryWithObject:countryCode forKey:NSLocaleCountryCode];
NSString *identifier = [NSLocale localeIdentifierFromComponents:countryDictionaryInstance];
NSString *country =
[[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:identifier];
NSMutableDictionary *countryMeta = [[NSMutableDictionary alloc] init];
if (country) {
[countryMeta setObject:country forKey:@"name"];
} else {
NSString *systemCountry =
[[NSLocale systemLocale] displayNameForKey:NSLocaleIdentifier value:identifier];
if (systemCountry) {
[countryMeta setObject:systemCountry forKey:@"name"];
}
}
if (countryCode) {
[countryMeta setObject:countryCode forKey:@"code"];
}
NBPhoneMetaData *metaData = [self getMetadataForRegion:countryCode];
if (metaData) {
[countryMeta setObject:metaData forKey:@"metadata"];
}
[resultMetadata addObject:countryMeta];
}
return resultMetadata;
}
+ (NSArray *)regionCodeFromCountryCode:(NSNumber *)countryCodeNumber {
NSArray *res = [self CN2CCodeMap][[countryCodeNumber stringValue]];
if ([res isKindOfClass:[NSArray class]] && [res count] > 0) {
return res;
}
return nil;
}
+ (NSString *)countryCodeFromRegionCode:(NSString *)regionCode {
return [self CCode2CNMap][regionCode];
}
/**
* Returns the metadata for the given region code or {@code nil} if the region
* code is invalid or unknown.
*
* @param {?string} regionCode
* @return {i18n.phonenumbers.PhoneMetadata}
*/
- (NBPhoneMetaData *)getMetadataForRegion:(NSString *)regionCode {
regionCode = StringByTrimming(regionCode);
if (regionCode.length == 0) {
return nil;
}
regionCode = [regionCode uppercaseString];
NBPhoneMetaData *cachedMetadata = [_metadataCache objectForKey:regionCode];
if (cachedMetadata != nil) {
return cachedMetadata;
}
NSDictionary *dict = [[self class] phoneNumberDataMap][@"countryToMetadata"];
NSArray *entry = dict[regionCode];
if (entry) {
NBPhoneMetaData *metadata = [[NBPhoneMetaData alloc] initWithEntry:entry];
[_metadataCache setObject:metadata forKey:regionCode];
return metadata;
}
return nil;
}
/**
* @param countryCallingCode countryCallingCode
* @return {i18n.phonenumbers.PhoneMetadata}
*/
- (NBPhoneMetaData *)getMetadataForNonGeographicalRegion:(NSNumber *)countryCallingCode {
NSString *countryCallingCodeStr = countryCallingCode.stringValue;
return [self getMetadataForRegion:countryCallingCodeStr];
}
+ (BOOL)hasValue:(NSString *)string {
string = StringByTrimming(string);
return string.length != 0;
}
#if SHORT_NUMBER_SUPPORT
+ (NSDictionary *)shortNumberDataMap {
static NSDictionary *shortNumberDataDictionary;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shortNumberDataDictionary =
[self jsonObjectFromZippedDataWithBytes:kShortNumberMetaData
compressedLength:kShortNumberMetaDataCompressedLength
expandedLength:kShortNumberMetaDataExpandedLength];
});
return shortNumberDataDictionary;
}
- (NBPhoneMetaData *)shortNumberMetadataForRegion:(NSString *)regionCode
{
regionCode = StringByTrimming(regionCode);
if (regionCode.length == 0) {
return nil;
}
regionCode = [regionCode uppercaseString];
NBPhoneMetaData *cachedMetadata = [_shortNumberMetadataCache objectForKey:regionCode];
if (cachedMetadata != nil) {
return cachedMetadata;
}
NSDictionary *dict = [[self class] shortNumberDataMap][@"countryToMetadata"];
NSArray *entry = dict[regionCode];
if (entry) {
NBPhoneMetaData *metadata = [[NBPhoneMetaData alloc] initWithEntry:entry];
[_shortNumberMetadataCache setObject:metadata forKey:regionCode];
return metadata;
}
return nil;
}
#endif // SHORT_NUMBER_SUPPORT
/**
* Expand gzipped data into a JSON object.
* @param bytes Array<Bytef> of zipped data.
* @param compressedLength Length of the compressed bytes.
* @param expandedLength Length of the expanded bytes.
* @return JSON dictionary.
*/
+ (NSDictionary *)jsonObjectFromZippedDataWithBytes:(z_const Bytef [])bytes
compressedLength:(NSUInteger)compressedLength
expandedLength:(NSUInteger)expandedLength {
// Data is a gzipped JSON file that is embedded in the binary.
// See GeneratePhoneNumberHeader.sh and PhoneNumberMetaData.h for details.
NSMutableData* gunzippedData = [NSMutableData dataWithLength:expandedLength];
z_stream zStream;
memset(&zStream, 0, sizeof(zStream));
__attribute((unused)) int err = inflateInit2(&zStream, 16);
NSAssert(err == Z_OK, @"Unable to init stream. err = %d", err);
zStream.next_in = bytes;
zStream.avail_in = (uint)compressedLength;
zStream.next_out = (Bytef *)gunzippedData.bytes;
zStream.avail_out = (uint)gunzippedData.length;
err = inflate(&zStream, Z_FINISH);
NSAssert(err == Z_STREAM_END, @"Unable to inflate compressed data. err = %d", err);
err = inflateEnd(&zStream);
NSAssert(err == Z_OK, @"Unable to inflate compressed data. err = %d", err);
NSError *error = nil;
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:gunzippedData
options:0
error:&error];
NSAssert(error == nil, @"Unable to convert JSON - %@", error);
return jsonObject;
}
@end