1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-12 09:29:55 +01:00
Telegram/legacy/TelegraphKit/RMPhoneFormat.m
2015-10-01 19:19:52 +03:00

755 lines
27 KiB
Objective-C

#import "RMPhoneFormat.h"
@interface PhoneRule : NSObject
@property (nonatomic, assign) int minVal;
@property (nonatomic, assign) int maxVal;
@property (nonatomic, assign) int byte8;
@property (nonatomic, assign) int maxLen;
@property (nonatomic, assign) int otherFlag;
@property (nonatomic, assign) int prefixLen;
@property (nonatomic, assign) int flag12;
@property (nonatomic, assign) int flag13;
@property (nonatomic) NSString *format;
#ifdef DEBUG
@property (nonatomic) NSSet *countries;
@property (nonatomic) NSString *callingCode;
@property (nonatomic, assign) int matchLen;
#endif
- (NSString *)format:(NSString *)str intlPrefix:(NSString *)intlPrefix trunkPrefix:(NSString *)trunkPrefix;
@end
@implementation PhoneRule
@synthesize minVal = _minVal;
@synthesize maxVal = _maxVal;
@synthesize byte8 = _byte8;
@synthesize maxLen = _maxLen;
@synthesize otherFlag = _otherFlag;
@synthesize prefixLen = _prefixLen;
@synthesize flag12 = _flag12;
@synthesize flag13 = _flag13;
@synthesize format = _format;
#ifdef DEBUG
@synthesize countries = _countries;
@synthesize callingCode = _callingCode;
@synthesize matchLen = _matchLen;
#endif
- (NSString *)format:(NSString *)str intlPrefix:(NSString *)intlPrefix trunkPrefix:(NSString *)trunkPrefix {
BOOL hadC = NO;
BOOL hadN = NO;
BOOL hasOpen = NO;
int spot = 0;
NSMutableString *res = [[NSMutableString alloc] initWithCapacity:20];
for (int i = 0; i < (int)[self.format length]; i++) {
unichar ch = [self.format characterAtIndex:i];
switch (ch) {
case 'c':
// Add international prefix if there is one.
hadC = YES;
if (intlPrefix != nil) {
[res appendString:intlPrefix];
}
break;
case 'n':
// Add trunk prefix if there is one.
hadN = YES;
if (trunkPrefix != nil) {
[res appendString:trunkPrefix];
}
break;
case '#':
// Add next digit from number. If there aren't enough digits left then do nothing unless we need to
// space-fill a pair of parenthesis.
if (spot < (int)[str length]) {
[res appendString:[str substringWithRange:NSMakeRange(spot, 1)]];
spot++;
} else if (hasOpen) {
[res appendString:@" "];
}
break;
case '(':
// Flag we found an open paren so it can be space-filled. But only do so if we aren't beyond the
// end of the number.
if (spot < (int)[str length]) {
hasOpen = YES;
}
// fall through
default: // rest like ) and -
// Don't show space after n if no trunkPrefix or after c if no intlPrefix
if (!(ch == ' ' && i > 0 && (([self.format characterAtIndex:i - 1] == 'n' && trunkPrefix == nil) || ([self.format characterAtIndex:i - 1] == 'c' && intlPrefix == nil)))) {
// Only show punctuation if not beyond the end of the supplied number.
// The only exception is to show a close paren if we had found
if (spot < (int)[str length] || (hasOpen && ch == ')')) {
[res appendString:[self.format substringWithRange:NSMakeRange(i, 1)]];
if (ch == ')') {
hasOpen = NO; // close it
}
}
}
break;
}
}
// Not all format strings have a 'c' or 'n' in them. If we have an international prefix or a trunk prefix but the
// format string doesn't explictly say where to put it then simply add it to the beginning.
if (intlPrefix != nil && !hadC) {
[res insertString:[NSString stringWithFormat:@"%@ ", intlPrefix] atIndex:0];
} else if (trunkPrefix != nil && !hadN) {
[res insertString:trunkPrefix atIndex:0];
}
return res;
}
- (NSString *)description {
#ifdef DEBUG
return [NSString stringWithFormat:@"Rule: { countries: %@, calling code: %@, matchlen: %d, minVal: %d, maxVal: %d, byte8: %d, maxLen: %d, nFlag: %d, prefixLen: %d, flag12: %d, flag13: %d, format: %@ }", self.countries, self.callingCode, self.matchLen, self.minVal, self.maxVal, self.byte8, self.maxLen, self.otherFlag, self.prefixLen, self.flag12, self.flag13, self.format];
#else
return [NSString stringWithFormat:@"Rule: { minVal: %d, maxVal: %d, byte8: %d, maxLen: %d, nFlag: %d, prefixLen: %d, flag12: %d, flag13: %d, format: %@ }", self.minVal, self.maxVal, self.byte8, self.maxLen, self.otherFlag, self.prefixLen, self.flag12, self.flag13, self.format];
#endif
}
@end
@interface RuleSet : NSObject
@property (nonatomic, assign) int matchLen;
@property (nonatomic) NSMutableArray *rules;
- (NSString *)format:(NSString *)str intlPrefix:(NSString *)intlPrefix trunkPrefix:(NSString *)trunkPrefix prefixRequired:(BOOL)prefixRequired;
@end
@implementation RuleSet
@synthesize matchLen = _matchLen;
@synthesize rules = _rules;
- (NSString *)format:(NSString *)str intlPrefix:(NSString *)intlPrefix trunkPrefix:(NSString *)trunkPrefix prefixRequired:(BOOL)prefixRequired {
if ((int)[str length] >= self.matchLen)
{
NSString *begin = [str substringToIndex:self.matchLen];
int val = [begin intValue];
for (PhoneRule *rule in self.rules)
{
if (val >= rule.minVal && val <= rule.maxVal && (int)[str length] <= rule.maxLen)
{
if (prefixRequired)
{
if (
((rule.flag12 & 0x03) == 0 &&
trunkPrefix == nil &&
intlPrefix == nil) ||
(trunkPrefix != nil && (rule.flag12 & 0x01)) ||
(intlPrefix != nil && (rule.flag12 & 0x02))
)
{
return [rule format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix];
}
}
else
{
if ((trunkPrefix == nil && intlPrefix == nil) || (trunkPrefix != nil && (rule.flag12 & 0x01)) || (intlPrefix != nil && (rule.flag12 & 0x02)))
{
return [rule format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix];
}
}
}
}
if (!prefixRequired)
{
if (intlPrefix != nil)
{
for (PhoneRule *rule in self.rules)
{
if (val >= rule.minVal && val <= rule.maxVal && (int)[str length] <= rule.maxLen)
{
if (trunkPrefix == nil || (rule.flag12 & 0x01))
{
return [rule format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix];
}
}
}
} else if (trunkPrefix != nil)
{
for (PhoneRule *rule in self.rules)
{
if (val >= rule.minVal && val <= rule.maxVal && (int)[str length] <= rule.maxLen)
{
if (intlPrefix == nil || (rule.flag12 & 0x02))
{
return [rule format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix];
}
}
}
}
}
return nil; // no match found
} else {
return nil; // not long enough to compare
}
}
- (NSString *)description {
NSMutableString *res = [NSMutableString stringWithCapacity:100];
[res appendFormat:@"RuleSet: { matchLen: %d, rules: %@ }", self.matchLen, self.rules];
return res;
}
@end
@interface CallingCodeInfo : NSObject
@property (nonatomic) NSSet *countries;
@property (nonatomic) NSString *callingCode;
@property (nonatomic) NSMutableArray *trunkPrefixes;
@property (nonatomic) NSMutableArray *intlPrefixes;
@property (nonatomic) NSMutableArray *ruleSets;
@property (nonatomic) NSMutableArray *formatStrings;
- (NSString *)matchingAccessCode:(NSString *)str;
- (NSString *)format:(NSString *)str;
@end
@implementation CallingCodeInfo
@synthesize countries = _countries;
@synthesize callingCode = _callingCode;
@synthesize trunkPrefixes = _trunkPrefixes;
@synthesize intlPrefixes = _intlPrefixes;
@synthesize ruleSets = _ruleSets;
@synthesize formatStrings = _formatStrings;
- (NSString *)matchingAccessCode:(NSString *)str {
for (NSString *code in self.intlPrefixes) {
if ([str hasPrefix:code]) {
return code;
}
}
return nil;
}
- (NSString *)matchingTrunkCode:(NSString *)str {
for (NSString *code in self.trunkPrefixes) {
if ([str hasPrefix:code]) {
return code;
}
}
return nil;
}
- (NSString *)format:(NSString *)orig
{
NSString *str = orig;
NSString *trunkPrefix = nil;
NSString *intlPrefix = nil;
if ([str hasPrefix:self.callingCode])
{
intlPrefix = self.callingCode;
str = [str substringFromIndex:[intlPrefix length]];
}
else
{
NSString *trunk = [self matchingTrunkCode:str];
if (trunk)
{
trunkPrefix = trunk;
str = [str substringFromIndex:[trunkPrefix length]];
}
}
for (RuleSet *set in self.ruleSets)
{
NSString *phone = [set format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix prefixRequired:YES];
if (phone)
{
return phone;
}
}
for (RuleSet *set in self.ruleSets)
{
NSString *phone = [set format:str intlPrefix:intlPrefix trunkPrefix:trunkPrefix prefixRequired:NO];
if (phone)
{
return phone;
}
}
if (intlPrefix != nil && [str length])
{
return [NSString stringWithFormat:@"%@ %@", intlPrefix, str];
}
return orig;
}
- (NSString *)description {
NSMutableString *res = [NSMutableString stringWithCapacity:100];
[res appendFormat:@"CountryInfo { countries: %@, code: %@, trunkPrefixes: %@, intlPrefixes: %@", self.countries, self.callingCode, self.trunkPrefixes, self.intlPrefixes];
[res appendFormat:@", rule sets: %@ }", self.ruleSets];
return res;
}
@end
static NSCharacterSet *phoneChars = nil;
#ifdef DEBUG
static NSMutableDictionary *extra1CallingCodes = nil;
static NSMutableDictionary *extra2CallingCodes = nil;
static NSMutableDictionary *extra3CallingCodes = nil;
static NSMutableDictionary *flagRules = nil;
#endif
@implementation RMPhoneFormat {
NSData *_data;
NSString *_defaultCountry;
NSString *_defaultCallingCode;
NSMutableDictionary *_callingCodeOffsets;
NSMutableDictionary *_callingCodeCountries;
NSMutableDictionary *_callingCodeData;
NSMutableDictionary *_countryCallingCode;
}
+ (void)initialize {
phoneChars = [NSCharacterSet characterSetWithCharactersInString:@"0123456789+*#"];
#ifdef DEBUG
extra1CallingCodes = [[NSMutableDictionary alloc] init];
extra2CallingCodes = [[NSMutableDictionary alloc] init];
extra3CallingCodes = [[NSMutableDictionary alloc] init];
flagRules = [[NSMutableDictionary alloc] init];
#endif
}
+ (NSString *)strip:(NSString *)str {
NSMutableString *res = [NSMutableString stringWithString:str];
for (int i = (int)[res length] - 1; i >= 0; i--) {
if (![phoneChars characterIsMember:[res characterAtIndex:i]]) {
[res deleteCharactersInRange:NSMakeRange(i, 1)];
}
}
return res;
}
+ (RMPhoneFormat *)instance {
static RMPhoneFormat *instance = nil;
static dispatch_once_t predicate = 0;
dispatch_once(&predicate, ^{ instance = [self new]; });
return instance;
}
- (id)init {
self = [self initWithDefaultCountry:nil];
return self;
}
- (id)initWithDefaultCountry:(NSString *)countryCode {
if ((self = [super init])) {
_data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"PhoneFormats" ofType:@"dat"]];
NSAssert(_data, @"The file PhoneFormats.dat is not in the resource bundle. See the README.");
if (countryCode.length) {
_defaultCountry = countryCode;
} else {
NSLocale *loc = [NSLocale currentLocale];
_defaultCountry = [[loc objectForKey:NSLocaleCountryCode] lowercaseString];
}
_callingCodeOffsets = [[NSMutableDictionary alloc] initWithCapacity:255];
_callingCodeCountries = [[NSMutableDictionary alloc] initWithCapacity:255];
_callingCodeData = [[NSMutableDictionary alloc] initWithCapacity:10];
_countryCallingCode = [[NSMutableDictionary alloc] initWithCapacity:255];
[self parseDataHeader];
}
return self;
}
- (NSString *)defaultCallingCode {
return [self callingCodeForCountryCode:_defaultCountry];
}
- (NSString *)callingCodeForCountryCode:(NSString *)countryCode {
return [_countryCallingCode objectForKey:[countryCode lowercaseString]];
}
- (NSSet *)countriesForCallingCode:(NSString *)callingCode {
if ([callingCode hasPrefix:@"+"]) {
callingCode = [callingCode substringFromIndex:1];
}
return [_callingCodeCountries objectForKey:callingCode];
}
- (CallingCodeInfo *)findCallingCodeInfo:(NSString *)str {
CallingCodeInfo *res = nil;
for (int i = 0; i < 3; i++) {
if (i < (int)[str length]) {
res = [self callingCodeInfo:[str substringToIndex:i + 1]];
if (res) {
break;
}
} else {
break;
}
}
return res;
}
- (NSString *)format:(NSString *)orig implicitPlus:(bool)implicitPlus
{
NSString *str = [RMPhoneFormat strip:orig];
bool hasPlusPrefix = [str hasPrefix:@"+"];
if ([str hasPrefix:@"+"] || implicitPlus)
{
NSString *rest = hasPlusPrefix ? [str substringFromIndex:1] : str;
CallingCodeInfo *info = [self findCallingCodeInfo:rest];
if (info)
{
NSString *phone = [info format:rest];
return [@"+" stringByAppendingString:phone];
} else
{
return orig;
}
}
else
{
CallingCodeInfo *info = [self callingCodeInfo:_defaultCallingCode];
if (info == nil)
{
return orig;
}
NSString *accessCode = [info matchingAccessCode:str];
if (accessCode)
{
NSString *rest = [str substringFromIndex:[accessCode length]];
NSString *phone = rest;
CallingCodeInfo *info2 = [self findCallingCodeInfo:rest];
if (info2)
{
phone = [info2 format:rest];
}
if ([phone length] == 0)
{
return accessCode;
}
else
{
return [NSString stringWithFormat:@"%@ %@", accessCode, phone];
}
}
else
{
NSString *phone = [info format:str];
return phone;
}
}
return orig;
}
- (uint32_t)value32:(NSUInteger)offset {
if (offset + 4 <= [_data length]) {
return OSReadLittleInt32([_data bytes], offset);
} else {
return 0;
}
}
- (int)value16:(NSUInteger)offset {
if (offset + 2 <= [_data length]) {
return OSReadLittleInt16([_data bytes], offset);
} else {
return 0;
}
}
- (int)value16BE:(NSUInteger)offset {
if (offset + 2 <= [_data length]) {
return OSReadBigInt16([_data bytes], offset);
} else {
return 0;
}
}
- (CallingCodeInfo *)callingCodeInfo:(NSString *)callingCode
{
CallingCodeInfo *res = [_callingCodeData objectForKey:callingCode];
if (res == nil)
{
NSNumber *num = [_callingCodeOffsets objectForKey:callingCode];
if (num)
{
const uint8_t *bytes = [_data bytes];
uint32_t start = (int)[num longValue];
uint32_t offset = start;
res = [[CallingCodeInfo alloc] init];
res.callingCode = callingCode;
res.countries = [_callingCodeCountries objectForKey:callingCode];
[_callingCodeData setObject:res forKey:callingCode];
uint16_t block1Len = (uint16_t)[self value16:offset];
offset += 2;
#ifdef DEBUG
uint16_t extra1 = (uint16_t)[self value16:offset];
#endif
offset += 2;
uint16_t block2Len = (uint16_t)[self value16:offset];
offset += 2;
#ifdef DEBUG
uint16_t extra2 = (uint16_t)[self value16:offset];
#endif
offset += 2;
uint16_t setCnt = (uint16_t)[self value16:offset];
offset += 2;
#ifdef DEBUG
uint16_t extra3 = (uint16_t)[self value16:offset];
#endif
offset += 2;
#ifdef DEBUG
if (extra1) {
NSMutableArray *vals = [extra1CallingCodes objectForKey:[NSNumber numberWithInt:extra1]];
if (!vals) {
vals = [[NSMutableArray alloc] init];
[extra1CallingCodes setObject:vals forKey:[NSNumber numberWithInt:extra1]];
}
[vals addObject:res];
}
if (extra2) {
NSMutableArray *vals = [extra2CallingCodes objectForKey:[NSNumber numberWithInt:extra2]];
if (!vals) {
vals = [[NSMutableArray alloc] init];
[extra2CallingCodes setObject:vals forKey:[NSNumber numberWithInt:extra2]];
}
[vals addObject:res];
}
if (extra3) {
NSMutableArray *vals = [extra3CallingCodes objectForKey:[NSNumber numberWithInt:extra3]];
if (!vals) {
vals = [[NSMutableArray alloc] init];
[extra3CallingCodes setObject:vals forKey:[NSNumber numberWithInt:extra3]];
}
[vals addObject:res];
}
#endif
NSMutableArray *strs = [NSMutableArray arrayWithCapacity:5];
NSString *str;
while ([(str = [NSString stringWithCString:(char *)bytes + offset encoding:NSUTF8StringEncoding]) length]) {
[strs addObject:str];
offset += [str length] + 1;
}
res.trunkPrefixes = strs;
offset++; // skip NULL
strs = [NSMutableArray arrayWithCapacity:5];
while ([(str = [NSString stringWithCString:(char *)bytes + offset encoding:NSUTF8StringEncoding]) length]) {
[strs addObject:str];
offset += [str length] + 1;
}
res.intlPrefixes = strs;
NSMutableArray *ruleSets = [NSMutableArray arrayWithCapacity:setCnt];
offset = start + block1Len; // Start of rule sets
for (int s = 0; s < setCnt; s++) {
RuleSet *ruleSet = [[RuleSet alloc] init];
int matchCnt = [self value16:offset];
ruleSet.matchLen = matchCnt;
offset += 2;
int ruleCnt = [self value16:offset];
offset += 2;
NSMutableArray *rules = [NSMutableArray arrayWithCapacity:ruleCnt];
for (int r = 0; r < ruleCnt; r++) {
PhoneRule *rule = [[PhoneRule alloc] init];
rule.minVal = [self value32:offset];
offset += 4;
rule.maxVal = [self value32:offset];
offset += 4;
rule.byte8 = (int)bytes[offset++];
rule.maxLen = (int)bytes[offset++];
rule.otherFlag = (int)bytes[offset++];
rule.prefixLen = (int)bytes[offset++];
rule.flag12 = (int)bytes[offset++];
rule.flag13 = (int)bytes[offset++];
uint16_t strOffset = (uint16_t)[self value16:offset];
offset += 2;
rule.format = [NSString stringWithCString:(char *)bytes + start + block1Len + block2Len + strOffset encoding:NSUTF8StringEncoding];
// Several formats contain [[9]] or [[8]]. Using the Contacts app as a test, I can find no use
// for these. Do they mean "optional"? They don't seem to have any use. This code strips out
// anything in [[..]]
NSRange openPos = [rule.format rangeOfString:@"[["];
if (openPos.location != NSNotFound) {
NSRange closePos = [rule.format rangeOfString:@"]]"];
rule.format = [NSString stringWithFormat:@"%@%@", [rule.format substringToIndex:openPos.location], [rule.format substringFromIndex:closePos.location + closePos.length]];
}
[rules addObject:rule];
#ifdef DEBUG
rule.countries = res.countries;
rule.callingCode = res.callingCode;
rule.matchLen = matchCnt;
if (rule.byte8) {
NSMutableDictionary *data = [flagRules objectForKey:@"byte8"];
if (!data) {
data = [[NSMutableDictionary alloc] init];
[flagRules setObject:data forKey:@"byte8"];
}
NSMutableArray *list = [data objectForKey:[NSNumber numberWithInt:rule.byte8]];
if (!list) {
list = [[NSMutableArray alloc] init];
[data setObject:list forKey:[NSNumber numberWithInt:rule.byte8]];
}
[list addObject:rule];
}
if (rule.prefixLen) {
NSMutableDictionary *data = [flagRules objectForKey:@"prefixLen"];
if (!data) {
data = [[NSMutableDictionary alloc] init];
[flagRules setObject:data forKey:@"prefixLen"];
}
NSMutableArray *list = [data objectForKey:[NSNumber numberWithInt:rule.prefixLen]];
if (!list) {
list = [[NSMutableArray alloc] init];
[data setObject:list forKey:[NSNumber numberWithInt:rule.prefixLen]];
}
[list addObject:rule];
}
if (rule.otherFlag) {
NSMutableDictionary *data = [flagRules objectForKey:@"otherFlag"];
if (!data) {
data = [[NSMutableDictionary alloc] init];
[flagRules setObject:data forKey:@"otherFlag"];
}
NSMutableArray *list = [data objectForKey:[NSNumber numberWithInt:rule.otherFlag]];
if (!list) {
list = [[NSMutableArray alloc] init];
[data setObject:list forKey:[NSNumber numberWithInt:rule.otherFlag]];
}
[list addObject:rule];
}
if (rule.flag12) {
NSMutableDictionary *data = [flagRules objectForKey:@"flag12"];
if (!data) {
data = [[NSMutableDictionary alloc] init];
[flagRules setObject:data forKey:@"flag12"];
}
NSMutableArray *list = [data objectForKey:[NSNumber numberWithInt:rule.flag12]];
if (!list) {
list = [[NSMutableArray alloc] init];
[data setObject:list forKey:[NSNumber numberWithInt:rule.flag12]];
}
[list addObject:rule];
}
if (rule.flag13) {
NSMutableDictionary *data = [flagRules objectForKey:@"flag13"];
if (!data) {
data = [[NSMutableDictionary alloc] init];
[flagRules setObject:data forKey:@"flag13"];
}
NSMutableArray *list = [data objectForKey:[NSNumber numberWithInt:rule.flag13]];
if (!list) {
list = [[NSMutableArray alloc] init];
[data setObject:list forKey:[NSNumber numberWithInt:rule.flag13]];
}
[list addObject:rule];
}
#endif
}
ruleSet.rules = rules;
[ruleSets addObject:ruleSet];
}
res.ruleSets = ruleSets;
}
}
return res;
}
- (void)parseDataHeader {
int count = [self value32:0];
uint32_t base = count * 12 + 4;
const void *bytes = [_data bytes];
NSUInteger spot = 4;
for (int i = 0; i < count; i++) {
NSString *callingCode = [NSString stringWithCString:bytes + spot encoding:NSUTF8StringEncoding];
spot += 4;
NSString *country = [NSString stringWithCString:bytes + spot encoding:NSUTF8StringEncoding];
spot += 4;
uint32_t offset = [self value32:spot] + base;
spot += 4;
if ([country isEqualToString:_defaultCountry]) {
_defaultCallingCode = callingCode;
}
[_countryCallingCode setObject:callingCode forKey:country];
[_callingCodeOffsets setObject:[NSNumber numberWithLong:offset] forKey:callingCode];
NSMutableSet *countries = [_callingCodeCountries objectForKey:callingCode];
if (!countries) {
countries = [[NSMutableSet alloc] init];
[_callingCodeCountries setObject:countries forKey:callingCode];
}
[countries addObject:country];
}
if (_defaultCallingCode) {
[self callingCodeInfo:_defaultCallingCode];
}
}
#ifdef DEBUG
- (void)dump {
NSArray *callingCodes = [[_callingCodeOffsets allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSString *callingCode in callingCodes) {
CallingCodeInfo *info = [self callingCodeInfo:callingCode];
NSLog(@"%@", info);
}
NSLog(@"flagRules: %@", flagRules);
NSLog(@"extra1 calling codes: %@", extra1CallingCodes);
NSLog(@"extra2 calling codes: %@", extra2CallingCodes);
NSLog(@"extra3 calling codes: %@", extra3CallingCodes);
}
#endif
@end