root/apps/iphone/superbook/trunk/Classes/DotTelABMapper.m
@
461
| Revision 461, 24.3 kB (checked in by henri, 4 years ago) |
|---|
| Line | |
|---|---|
| 1 | // |
| 2 | // DotTelABMapper.m |
| 3 | // LocateThem |
| 4 | // |
| 5 | // Created by Henri Asseily on 5/27/09. |
| 6 | /* |
| 7 | Copyright (c) 2008-2009, Telnic Ltd. All rights reserved. |
| 8 | |
| 9 | Redistribution and use in source and binary forms, with or without modification, |
| 10 | are permitted provided that the following conditions are met: |
| 11 | |
| 12 | Redistributions of source code must retain the above copyright notice, this list of conditions |
| 13 | and the following disclaimer. Redistributions in binary form must reproduce the above copyright |
| 14 | notice, this list of conditions and the following disclaimer in the documentation and/or other |
| 15 | materials provided with the distribution. |
| 16 | Neither the name of the Telnic Ltd. nor the names of its contributors may be used to endorse or |
| 17 | promote products derived from this software without specific prior written permission. |
| 18 | THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS |
| 19 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
| 20 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| 24 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 25 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | */ |
| 27 | // |
| 28 | |
| 29 | #import "DotTelABMapper.h" |
| 30 | |
| 31 | // Define initialization and assignment of any ABMultiValue field that contains CFStringRefs |
| 32 | // Saves a lot of typing |
| 33 | #define SETNAPTR_INITIALIZATION NSError *err; \ |
| 34 | CFErrorRef cferr = (CFErrorRef)err; \ |
| 35 | NSString *service; \ |
| 36 | service = [[rec serviceTypeArray] objectAtIndex:0]; \ |
| 37 | NSString *lih; \ |
| 38 | if ([[rec lihArray] count] == 0) \ |
| 39 | lih = @""; \ |
| 40 | else \ |
| 41 | lih = [[rec lihArray] objectAtIndex:0]; \ |
| 42 | ABMultiValueRef allexisting; \ |
| 43 | ABMutableMultiValueRef allupdated; \ |
| 44 | CFStringRef abLabel; \ |
| 45 | CFStringRef abValue |
| 46 | |
| 47 | #define SETNAPTR_ASSIGN(kValType) BOOL exists = NO; \ |
| 48 | for (CFIndex i = 0; i < ABMultiValueGetCount(allupdated); i++) { \ |
| 49 | CFStringRef aVal = (CFStringRef)ABMultiValueCopyValueAtIndex(allupdated, i); \ |
| 50 | if (aVal) { \ |
| 51 | if ([self record:(NSString *)abValue isEqualToRecord:(NSString *)aVal forProperty:kValType]) { \ |
| 52 | exists = YES; \ |
| 53 | CFRelease(aVal); \ |
| 54 | break; \ |
| 55 | } \ |
| 56 | CFRelease(aVal); \ |
| 57 | } \ |
| 58 | } \ |
| 59 | if (!exists) { \ |
| 60 | ABMultiValueAddValueAndLabel(allupdated, abValue, abLabel, NULL); \ |
| 61 | BOOL res = ABRecordSetValue(abRecord, kValType, allupdated, &cferr); \ |
| 62 | if (res) [updatedProperties addObject:[NSNumber numberWithInt:kValType]]; \ |
| 63 | } \ |
| 64 | CFRelease(allupdated); \ |
| 65 | return NULL |
| 66 | |
| 67 | @interface DotTelABMapper (PrivateMethods) |
| 68 | |
| 69 | - (NSError *)setNaptrField:(RecordNaptr *)field; |
| 70 | - (NSError *)setTxtField:(RecordTxt *)field; |
| 71 | - (NSError *)setLocField:(RecordLoc *)field; |
| 72 | |
| 73 | @end |
| 74 | |
| 75 | NSString * const DotTelABMapperErrorDomain = @"DotTelABMapperErrorDomain"; |
| 76 | static NSDictionary *naptrSelectors; |
| 77 | static NSDictionary *txtSelectors; |
| 78 | static NSDictionary *ccMapper; |
| 79 | |
| 80 | @implementation DotTelABMapper |
| 81 | |
| 82 | @synthesize updatedProperties; |
| 83 | |
| 84 | #pragma mark ---- designated initializer ---- |
| 85 | - (DotTelABMapper *)initWithABRecord:(ABRecordRef)rec { |
| 86 | if (rec == NULL) { |
| 87 | return NULL; |
| 88 | } |
| 89 | self = [super init]; |
| 90 | if (abRecord) { |
| 91 | CFRelease(abRecord); |
| 92 | } |
| 93 | abRecord = CFRetain(rec); |
| 94 | if (updatedProperties) { |
| 95 | [updatedProperties release]; |
| 96 | } |
| 97 | updatedProperties = [[NSMutableArray array] retain]; |
| 98 | |
| 99 | NSDictionary *selectorsMapper = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ABMapperSelectors" |
| 100 | ofType:@"plist"]]; |
| 101 | txtSelectors = [[selectorsMapper objectForKey:@"txt"] retain]; |
| 102 | naptrSelectors = [[selectorsMapper objectForKey:@"naptr"] retain]; |
| 103 | ccMapper = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"CountryCodeMapper" |
| 104 | ofType:@"plist"]]; |
| 105 | return self; |
| 106 | } |
| 107 | |
| 108 | - (DotTelABMapper *)init { |
| 109 | // fail |
| 110 | return NULL; |
| 111 | } |
| 112 | |
| 113 | #pragma mark ---- mapping entry methods ---- |
| 114 | |
| 115 | - (NSError *)setTelName:(NSString *)telname { |
| 116 | // Set a new telname or update it if it exists and is different |
| 117 | BOOL fieldExists = NO; |
| 118 | BOOL fieldChanged = NO; |
| 119 | CFStringRef telUrlValue; |
| 120 | ABMultiValueRef allExistingUrls; |
| 121 | ABMutableMultiValueRef allUrls; |
| 122 | allExistingUrls = ABRecordCopyValue(abRecord, kABPersonURLProperty); |
| 123 | if (!allExistingUrls) { // No URLs, so telname doesn't exist. Make a new property to hold the telname |
| 124 | allUrls = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 125 | } else { // There are URLs, let's find the telname |
| 126 | allUrls = ABMultiValueCreateMutableCopy(allExistingUrls); |
| 127 | CFRelease(allExistingUrls); |
| 128 | for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) { |
| 129 | telUrlValue = ABMultiValueCopyValueAtIndex(allUrls, j); |
| 130 | if ([(NSString *)telUrlValue hasSuffix:@".tel"] || [(NSString *)telUrlValue hasSuffix:@".tel/"]) { |
| 131 | fieldExists = YES; |
| 132 | // Found a telname. If it's different, update it |
| 133 | if (![telname isEqualToString:(NSString *)telUrlValue]) { |
| 134 | ABMultiValueReplaceValueAtIndex(allUrls, (CFStringRef)telname, j); |
| 135 | fieldChanged = YES; |
| 136 | } |
| 137 | CFRelease(telUrlValue); |
| 138 | break; |
| 139 | } |
| 140 | CFRelease(telUrlValue); |
| 141 | } |
| 142 | } |
| 143 | // If telname doesn't exist, create a new one |
| 144 | if (!fieldExists) { |
| 145 | ABMultiValueAddValueAndLabel(allUrls, (CFStringRef)telname, CFSTR(".tel"), NULL); |
| 146 | fieldChanged = YES; |
| 147 | } |
| 148 | NSError *err = nil; |
| 149 | CFErrorRef cferr = (CFErrorRef)err; |
| 150 | if (fieldChanged) { |
| 151 | BOOL res = ABRecordSetValue(abRecord, kABPersonURLProperty, allUrls, &cferr); |
| 152 | if (res) { |
| 153 | //[updatedProperties addObject:[NSNumber numberWithInt:kABPersonURLProperty]]; // Always show the .tel url |
| 154 | } |
| 155 | } |
| 156 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonURLProperty]]; // as said above, always show .tel url |
| 157 | CFRelease(allUrls); |
| 158 | |
| 159 | return err; |
| 160 | } |
| 161 | |
| 162 | - (NSError *)setField:(id)field { |
| 163 | if ([field isKindOfClass:[RecordNaptr class]]) |
| 164 | return [self setNaptrField:(RecordNaptr *)field]; |
| 165 | if ([field isKindOfClass:[RecordTxt class]]) |
| 166 | return [self setTxtField:(RecordTxt *)field]; |
| 167 | if ([field isKindOfClass:[RecordLoc class]]) |
| 168 | return [self setLocField:(RecordLoc *)field]; |
| 169 | |
| 170 | // wrong class |
| 171 | NSError *err = [NSError errorWithDomain:DotTelABMapperErrorDomain code:kDotTelABMapperUnknownRecordType userInfo:nil]; |
| 172 | return err; |
| 173 | } |
| 174 | |
| 175 | #pragma mark ---- private naptr/txt/loc dispatch methods --- |
| 176 | |
| 177 | - (NSError *)setNaptrField:(RecordNaptr *)field { |
| 178 | if ([field isTerminal]) { |
| 179 | NSString *primaryType = [[field serviceTypeArray] objectAtIndex:0]; |
| 180 | SEL parseMethod = NSSelectorFromString([naptrSelectors objectForKey:primaryType]); |
| 181 | if (parseMethod == (SEL)0) |
| 182 | return NULL; |
| 183 | return ((NSError *)[self performSelector:parseMethod withObject:field]); |
| 184 | } |
| 185 | return NULL; |
| 186 | } |
| 187 | |
| 188 | - (NSError *)setTxtField:(RecordTxt *)field { |
| 189 | if (field.isKeyword) { |
| 190 | NSArray *kv = field.keysAndValues; |
| 191 | NSString *primaryType = [kv objectAtIndex:0]; |
| 192 | SEL parseMethod = NSSelectorFromString([txtSelectors objectForKey:primaryType]); |
| 193 | if (parseMethod == (SEL)0) |
| 194 | return NULL; |
| 195 | return ((NSError *)[self performSelector:parseMethod withObject:kv]); |
| 196 | } |
| 197 | return NULL; |
| 198 | } |
| 199 | |
| 200 | - (NSError *)setLocField:(RecordLoc *)field { |
| 201 | // Do nothing for now |
| 202 | // We could do reverse geoloc and grab the actual address, but it's probably |
| 203 | // not what the .tel owner intended |
| 204 | return NULL; |
| 205 | } |
| 206 | |
| 207 | #pragma mark ---- private mapping methods --- |
| 208 | |
| 209 | - (NSError *)setNaptrNone:(RecordNaptr *)rec { |
| 210 | return NULL; |
| 211 | } |
| 212 | |
| 213 | - (NSError *)setNaptrPhone:(RecordNaptr *)rec { |
| 214 | SETNAPTR_INITIALIZATION; |
| 215 | |
| 216 | // ==== Begin parsing ==== |
| 217 | allexisting = ABRecordCopyValue(abRecord, kABPersonPhoneProperty); |
| 218 | if (!allexisting) { |
| 219 | allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 220 | } else { |
| 221 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 222 | CFRelease(allexisting); |
| 223 | } |
| 224 | abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 225 | if ([lih isEqualToString:@"x-mobile"]) { |
| 226 | abLabel = kABPersonPhoneMobileLabel; |
| 227 | } else if ([lih isEqualToString:@"x-main"]) { |
| 228 | abLabel = kABPersonPhoneMainLabel; |
| 229 | } else if ([lih isEqualToString:@"x-home"]) { |
| 230 | abLabel = kABHomeLabel; |
| 231 | } else if ([lih isEqualToString:@"x-work"]) { |
| 232 | abLabel = kABWorkLabel; |
| 233 | } else { |
| 234 | abLabel = kABOtherLabel; |
| 235 | } |
| 236 | // ==== End parsing ==== |
| 237 | |
| 238 | SETNAPTR_ASSIGN(kABPersonPhoneProperty); |
| 239 | } |
| 240 | |
| 241 | - (NSError *)setNaptrFax:(RecordNaptr *)rec { |
| 242 | SETNAPTR_INITIALIZATION; |
| 243 | |
| 244 | // ==== Begin parsing ==== |
| 245 | allexisting = ABRecordCopyValue(abRecord, kABPersonPhoneProperty); |
| 246 | if (!allexisting) { |
| 247 | allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 248 | } else { |
| 249 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 250 | CFRelease(allexisting); |
| 251 | } |
| 252 | abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 253 | if ([lih isEqualToString:@"x-main"]) { |
| 254 | abLabel = (CFStringRef)NSLocalizedString(@"Main Fax", @"'Main Fax' label"); |
| 255 | } else if ([lih isEqualToString:@"x-home"]) { |
| 256 | abLabel = kABPersonPhoneHomeFAXLabel; |
| 257 | } else { |
| 258 | abLabel = kABPersonPhoneWorkFAXLabel; |
| 259 | } |
| 260 | // ==== End parsing ==== |
| 261 | |
| 262 | SETNAPTR_ASSIGN(kABPersonPhoneProperty); |
| 263 | } |
| 264 | |
| 265 | - (NSError *)setNaptrEmail:(RecordNaptr *)rec { |
| 266 | SETNAPTR_INITIALIZATION; |
| 267 | |
| 268 | // ==== Begin parsing ==== |
| 269 | allexisting = ABRecordCopyValue(abRecord, kABPersonEmailProperty); |
| 270 | if (!allexisting) { |
| 271 | allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 272 | } else { |
| 273 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 274 | CFRelease(allexisting); |
| 275 | } |
| 276 | abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 277 | if ([lih isEqualToString:@"x-main"]) { |
| 278 | abLabel = (CFStringRef)NSLocalizedString(@"main", @"Label 'Main' for contact info"); |
| 279 | } else if ([lih isEqualToString:@"x-home"]) { |
| 280 | abLabel = kABHomeLabel; |
| 281 | } else if ([lih isEqualToString:@"x-work"]) { |
| 282 | abLabel = kABWorkLabel; |
| 283 | } else { |
| 284 | abLabel = kABOtherLabel; |
| 285 | } |
| 286 | // ==== End parsing ==== |
| 287 | |
| 288 | SETNAPTR_ASSIGN(kABPersonEmailProperty); |
| 289 | } |
| 290 | |
| 291 | - (NSError *)setNaptrUrl:(RecordNaptr *)rec { |
| 292 | SETNAPTR_INITIALIZATION; |
| 293 | |
| 294 | // ==== Begin parsing ==== |
| 295 | allexisting = ABRecordCopyValue(abRecord, kABPersonURLProperty); |
| 296 | if (!allexisting) { |
| 297 | allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 298 | } else { |
| 299 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 300 | CFRelease(allexisting); |
| 301 | } |
| 302 | |
| 303 | abValue = (CFStringRef)[rec uriContent]; |
| 304 | if ([lih isEqualToString:@"x-main"]) { |
| 305 | abLabel = kABPersonHomePageLabel; |
| 306 | } else if ([lih isEqualToString:@"x-home"]) { |
| 307 | abLabel = kABPersonHomePageLabel; |
| 308 | } else if ([lih isEqualToString:@"x-work"]) { |
| 309 | abLabel = kABWorkLabel; |
| 310 | } else { |
| 311 | abLabel = kABOtherLabel; |
| 312 | } |
| 313 | // ==== End parsing ==== |
| 314 | |
| 315 | SETNAPTR_ASSIGN(kABPersonURLProperty); |
| 316 | } |
| 317 | |
| 318 | - (NSError *)setNaptrUrlOther:(RecordNaptr *)rec { |
| 319 | SETNAPTR_INITIALIZATION; |
| 320 | |
| 321 | // ==== Begin parsing ==== |
| 322 | allexisting = ABRecordCopyValue(abRecord, kABPersonURLProperty); |
| 323 | if (!allexisting) { |
| 324 | allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType); |
| 325 | } else { |
| 326 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 327 | CFRelease(allexisting); |
| 328 | } |
| 329 | |
| 330 | abValue = (CFStringRef)[rec uriContent]; |
| 331 | NSString *servicePostfix = [[service componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 332 | if ([lih isEqualToString:@"x-main"]) { |
| 333 | abLabel = (CFStringRef)[servicePostfix stringByAppendingFormat:@" %@",NSLocalizedString(@"main", @"Label 'Main' for contact info")]; |
| 334 | } else if ([lih isEqualToString:@"x-home"]) { |
| 335 | abLabel = (CFStringRef)[servicePostfix stringByAppendingFormat:@" %@",(NSString *)kABHomeLabel]; |
| 336 | } else if ([lih isEqualToString:@"x-work"]) { |
| 337 | abLabel = (CFStringRef)[servicePostfix stringByAppendingFormat:@" %@",(NSString *)kABWorkLabel]; |
| 338 | } else { |
| 339 | abLabel = (CFStringRef)[servicePostfix stringByAppendingFormat:@" %@",(NSString *)kABOtherLabel]; |
| 340 | } |
| 341 | // ==== End parsing ==== |
| 342 | |
| 343 | SETNAPTR_ASSIGN(kABPersonURLProperty); |
| 344 | } |
| 345 | |
| 346 | - (NSError *)setNaptrIM:(RecordNaptr *)rec { |
| 347 | // NOTE: The simulator does NOT support IM fields. It silently discards them. |
| 348 | // IM fields are dictionaries, not strings. They need special handling. |
| 349 | SETNAPTR_INITIALIZATION; |
| 350 | |
| 351 | CFMutableDictionaryRef anIM = (CFMutableDictionaryRef)[NSMutableDictionary dictionaryWithCapacity:2]; // IM dictionary |
| 352 | |
| 353 | // ==== Begin parsing ==== |
| 354 | allexisting = ABRecordCopyValue(abRecord, kABPersonInstantMessageProperty); |
| 355 | if (!allexisting) { |
| 356 | allupdated = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType); |
| 357 | } else { |
| 358 | allupdated = ABMultiValueCreateMutableCopy(allexisting); |
| 359 | CFRelease(allexisting); |
| 360 | } |
| 361 | |
| 362 | abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 363 | if ([lih isEqualToString:@"x-main"]) { |
| 364 | abLabel = (CFStringRef)NSLocalizedString(@"main", @"Label 'Main' for contact info"); |
| 365 | } else if ([lih isEqualToString:@"x-home"]) { |
| 366 | abLabel = kABHomeLabel; |
| 367 | } else if ([lih isEqualToString:@"x-work"]) { |
| 368 | abLabel = kABWorkLabel; |
| 369 | } else { |
| 370 | abLabel = kABOtherLabel; |
| 371 | } |
| 372 | NSString *servicePostfix = [[service componentsSeparatedByString:@":"] objectAtIndex:1]; |
| 373 | if ([servicePostfix isEqualToString:@"aim"]) { |
| 374 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceAIM); |
| 375 | } else if ([servicePostfix isEqualToString:@"icq"]) { |
| 376 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceICQ); |
| 377 | } else if ([servicePostfix isEqualToString:@"ymsgr"]) { |
| 378 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceYahoo); |
| 379 | } else if ([servicePostfix isEqualToString:@"msnim"]) { |
| 380 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceMSN); |
| 381 | } else if ([servicePostfix isEqualToString:@"xmpp"]) { |
| 382 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceJabber); |
| 383 | } else if ([servicePostfix isEqualToString:@"gtalk"]) { |
| 384 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, @"GoogleTalk"); |
| 385 | } else { |
| 386 | CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, (CFStringRef)servicePostfix); |
| 387 | } |
| 388 | CFDictionaryAddValue(anIM, kABPersonInstantMessageUsernameKey, abValue); |
| 389 | // ==== End parsing ==== |
| 390 | |
| 391 | // ==== Begin assignment ==== |
| 392 | // Check if an IM with the same service type and label already exists. If so, we replace it. |
| 393 | // This is different behavior than phone numbers and urls (i.e. CFStringRefs) |
| 394 | // Note that we only replace the first one of the same type and label |
| 395 | CFIndex existingIMServiceIndex = kCFNotFound; |
| 396 | for (CFIndex i = 0; i < ABMultiValueGetCount(allupdated); i++) { |
| 397 | CFDictionaryRef existingIM = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(allupdated, i); |
| 398 | if (existingIM) { |
| 399 | if (CFDictionaryGetValue(anIM, kABPersonInstantMessageServiceKey) == CFDictionaryGetValue(existingIM, kABPersonInstantMessageServiceKey)) { |
| 400 | // Found a matching IM service type |
| 401 | CFStringRef existingLabel = (CFStringRef)ABMultiValueCopyLabelAtIndex(allupdated, i); |
| 402 | if (existingLabel) { |
| 403 | if (CFStringCompare(abLabel, existingLabel, 0) == kCFCompareEqualTo) { |
| 404 | // Found a matching label on top of the matching IM service type |
| 405 | ABMultiValueReplaceValueAtIndex(allupdated, anIM, i); |
| 406 | existingIMServiceIndex = i; |
| 407 | CFRelease(existingLabel); |
| 408 | CFRelease(existingIM); |
| 409 | break; |
| 410 | } else { |
| 411 | // There was a label but it didn't match, don't replace anything |
| 412 | CFRelease(existingLabel); |
| 413 | } |
| 414 | } else { |
| 415 | // No label, but the service type matches. Let's replace and update the label |
| 416 | ABMultiValueReplaceValueAtIndex(allupdated, anIM, i); |
| 417 | ABMultiValueReplaceLabelAtIndex(allupdated, abLabel, i); |
| 418 | existingIMServiceIndex = i; |
| 419 | CFRelease(existingIM); |
| 420 | break; |
| 421 | } |
| 422 | } |
| 423 | // At this point, the IM service type didn't match. Keep looping to look for a possible match |
| 424 | CFRelease(existingIM); |
| 425 | } |
| 426 | } |
| 427 | if (existingIMServiceIndex == kCFNotFound) { |
| 428 | // Didn't find a matching IM service type and label |
| 429 | ABMultiValueAddValueAndLabel(allupdated, anIM, abLabel, NULL); |
| 430 | BOOL res = ABRecordSetValue(abRecord, kABPersonInstantMessageProperty, allupdated, &cferr); |
| 431 | if (res) { |
| 432 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonInstantMessageProperty]]; |
| 433 | } |
| 434 | |
| 435 | } |
| 436 | CFRelease(allupdated); |
| 437 | return NULL; |
| 438 | } |
| 439 | |
| 440 | - (NSError *)setTxtNL:(NSArray *)kv { |
| 441 | // Name Parsing |
| 442 | // Discard the label |
| 443 | /* |
| 444 | "s" = "Salutation"; // e.g. "Mr", "Mrs", etc |
| 445 | "fn" = "First Name"; // e.g. "Adam" |
| 446 | "ln" = "Last Name"; // e.g. "Smith" |
| 447 | "nn" = "Nickname"; // e.g. "Ade" |
| 448 | */ |
| 449 | CFErrorRef err = NULL; |
| 450 | NSUInteger i, count = [kv count]; |
| 451 | for (i = 0; i < count-1; i=i+2) { |
| 452 | if (err) { |
| 453 | return (NSError *)err; |
| 454 | } |
| 455 | NSString *k = [kv objectAtIndex:i]; // key |
| 456 | NSString *v = [kv objectAtIndex:i+1]; // value |
| 457 | |
| 458 | if ([k isEqualToString:@"fn"]) { // kABPersonFirstNameProperty |
| 459 | if (ABRecordSetValue(abRecord, kABPersonFirstNameProperty, (CFStringRef)v, &err)) |
| 460 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonFirstNameProperty]]; |
| 461 | continue; |
| 462 | } |
| 463 | if ([k isEqualToString:@"ln"]) { // kABPersonLastNameProperty |
| 464 | if (ABRecordSetValue(abRecord, kABPersonLastNameProperty, (CFStringRef)v, &err)) |
| 465 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonLastNameProperty]]; |
| 466 | continue; |
| 467 | } |
| 468 | if ([k isEqualToString:@"nn"]) { // kABPersonNicknameProperty |
| 469 | if (ABRecordSetValue(abRecord, kABPersonNicknameProperty, (CFStringRef)v, &err)) |
| 470 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonNicknameProperty]]; |
| 471 | continue; |
| 472 | } |
| 473 | if ([k isEqualToString:@"s"]) { // kABPersonPrefixProperty |
| 474 | if (ABRecordSetValue(abRecord, kABPersonPrefixProperty, (CFStringRef)v, &err)) |
| 475 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonPrefixProperty]]; |
| 476 | continue; |
| 477 | } |
| 478 | } |
| 479 | return NULL; |
| 480 | } |
| 481 | |
| 482 | - (NSError *)setTxtBI:(NSArray *)kv { |
| 483 | return NULL; |
| 484 | } |
| 485 | |
| 486 | - (NSError *)setTxtPA:(NSArray *)kv { |
| 487 | // Used for both "pa" and "bpa" |
| 488 | /* |
| 489 | "a1" = "Address Line 1"; // e.g. "8 Wilfred Street" |
| 490 | "a2" = "Address Line 2"; // e.g. "Victoria" |
| 491 | "a3" = "Address Line 3"; // e.g. "Westminster" |
| 492 | "tc" = "City"; // e.g. "London" |
| 493 | "sp" = "State"; // e.g. "Hampshire" |
| 494 | "pc" = "Postal Code"; // e.g. "SW1E 6PL" |
| 495 | "c" = "Country"; // e.g. "Scotland" |
| 496 | */ |
| 497 | |
| 498 | // Create the address dictionary |
| 499 | NSMutableDictionary *newAddress = [NSMutableDictionary dictionaryWithCapacity:5]; |
| 500 | NSString *primaryType = [kv objectAtIndex:0]; |
| 501 | // NSString *primaryValue = [kv objectAtIndex:1]; // unused |
| 502 | |
| 503 | // Map the pa and bpa to home and work respectively |
| 504 | CFStringRef primaryLabel; |
| 505 | if ([primaryType isEqualToString:@"pa"]) { |
| 506 | primaryLabel = kABHomeLabel; |
| 507 | } else if ([primaryType isEqualToString:@"bpa"]) { |
| 508 | primaryLabel = kABWorkLabel; |
| 509 | } else { |
| 510 | return NULL; |
| 511 | } |
| 512 | |
| 513 | // Grab all types and values and build the newAddress |
| 514 | NSUInteger i, count = [kv count]; |
| 515 | NSMutableString *addressStreetFull = nil; |
| 516 | for (i = 0; i < count-1; i=i+2) { |
| 517 | NSString *k = [kv objectAtIndex:i]; // key |
| 518 | NSString *v = [kv objectAtIndex:i+1]; // value |
| 519 | |
| 520 | // Concatenate the address lines |
| 521 | if ([k isEqualToString:@"a1"]) { |
| 522 | if (addressStreetFull) { |
| 523 | // if it exists (it shouldn't), prepend a1 |
| 524 | [addressStreetFull insertString:[NSString stringWithFormat:@"%@\n", v] atIndex:0]; |
| 525 | } else { |
| 526 | addressStreetFull = [NSMutableString stringWithString:v]; |
| 527 | } |
| 528 | continue; |
| 529 | } |
| 530 | if ([k isEqualToString:@"a2"] || [k isEqualToString:@"a3"]) { |
| 531 | if (addressStreetFull) { |
| 532 | [addressStreetFull appendFormat:@"\n%@", v]; |
| 533 | } else { |
| 534 | addressStreetFull = [NSMutableString stringWithString:v]; |
| 535 | } |
| 536 | continue; |
| 537 | } |
| 538 | if ([k isEqualToString:@"tc"]) { // kABPersonAddressCityKey |
| 539 | [newAddress setObject:v forKey:(NSString *)kABPersonAddressCityKey]; |
| 540 | continue; |
| 541 | } |
| 542 | if ([k isEqualToString:@"sp"]) { // kABPersonAddressStateKey |
| 543 | [newAddress setObject:v forKey:(NSString *)kABPersonAddressStateKey]; |
| 544 | continue; |
| 545 | } |
| 546 | if ([k isEqualToString:@"pc"]) { // kABPersonAddressZIPKey |
| 547 | [newAddress setObject:v forKey:(NSString *)kABPersonAddressZIPKey]; |
| 548 | continue; |
| 549 | } |
| 550 | if ([k isEqualToString:@"c"]) { // kABPersonAddressCountryKey |
| 551 | [newAddress setObject:v forKey:(NSString *)kABPersonAddressCountryKey]; |
| 552 | NSString *countryCode = [ccMapper objectForKey:v]; |
| 553 | if (countryCode) { |
| 554 | [newAddress setObject:countryCode forKey:(NSString *)kABPersonAddressCountryCodeKey]; |
| 555 | } |
| 556 | continue; |
| 557 | } |
| 558 | } |
| 559 | // Add the concatenated address line |
| 560 | // kABPersonAddressStreetKey |
| 561 | if (addressStreetFull) { |
| 562 | [newAddress setObject:addressStreetFull forKey:(NSString *)kABPersonAddressStreetKey]; |
| 563 | } |
| 564 | |
| 565 | // Now match to an existing address and replace if found |
| 566 | // Otherwise create a new one |
| 567 | |
| 568 | BOOL fieldExists = NO; |
| 569 | BOOL fieldChanged = NO; |
| 570 | ABMultiValueRef existingaddresses; // All existing addresses |
| 571 | ABMutableMultiValueRef addresses; // All addresses |
| 572 | CFDictionaryRef anAddress; // An existing address |
| 573 | CFStringRef aLabel; // Label for that address |
| 574 | existingaddresses = ABRecordCopyValue(abRecord, kABPersonAddressProperty); |
| 575 | if (!existingaddresses) { // No addresses. Make a new property to hold the new address |
| 576 | addresses = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType); |
| 577 | } else { // There are addresses, let's find the first matching one that has the proper label |
| 578 | addresses = ABMultiValueCreateMutableCopy(existingaddresses); |
| 579 | CFRelease(existingaddresses); |
| 580 | for (CFIndex j = 0; j < ABMultiValueGetCount(addresses); j++) { |
| 581 | anAddress = ABMultiValueCopyValueAtIndex(addresses, j); |
| 582 | aLabel = ABMultiValueCopyLabelAtIndex(addresses, j); |
| 583 | if (CFStringCompare(primaryLabel, aLabel, kCFCompareCaseInsensitive) == kCFCompareEqualTo) { |
| 584 | fieldExists = YES; |
| 585 | // Found a matching address. Replace it without checking if it's the same |
| 586 | // It would be better to check similarity and not replace if equal, but the cost is high |
| 587 | ABMultiValueReplaceValueAtIndex(addresses, newAddress, j); |
| 588 | fieldChanged = YES; |
| 589 | } |
| 590 | CFRelease(anAddress); |
| 591 | if (aLabel) |
| 592 | CFRelease(aLabel); |
| 593 | if (fieldExists) |
| 594 | break; |
| 595 | } |
| 596 | } |
| 597 | // If field doesn't exist, create a new one |
| 598 | if (!fieldExists) { |
| 599 | ABMultiValueAddValueAndLabel(addresses, (CFDictionaryRef)newAddress, primaryLabel, NULL); |
| 600 | fieldChanged = YES; |
| 601 | } |
| 602 | NSError *err; |
| 603 | CFErrorRef cferr = (CFErrorRef)err; |
| 604 | if (fieldChanged) { |
| 605 | BOOL res = ABRecordSetValue(abRecord, kABPersonAddressProperty, addresses, &cferr); |
| 606 | if (res) { |
| 607 | [updatedProperties addObject:[NSNumber numberWithInt:kABPersonAddressProperty]]; |
| 608 | } |
| 609 | } |
| 610 | CFRelease(addresses); |
| 611 | |
| 612 | return NULL; |
| 613 | } |
| 614 | |
| 615 | #pragma mark ---- utility methods ---- |
| 616 | |
| 617 | - (BOOL)record:(NSString *)aRec isEqualToRecord:(NSString *)otherRec forProperty:(ABPropertyID)property { |
| 618 | if (property == kABPersonPhoneProperty) |
| 619 | return [self phone:aRec isEqualToPhone:otherRec]; |
| 620 | return [aRec isEqualToString:otherRec]; |
| 621 | } |
| 622 | |
| 623 | - (BOOL)phone:(NSString *)aPhone isEqualToPhone:(NSString *)otherPhone { |
| 624 | char *p1 = (char *)[aPhone cStringUsingEncoding:NSUTF8StringEncoding]; |
| 625 | char *p2 = (char *)[otherPhone cStringUsingEncoding:NSUTF8StringEncoding]; |
| 626 | BOOL areEqual = TRUE; |
| 627 | int i = 0; |
| 628 | int j = 0; |
| 629 | while ((char)p1[i] != '\0') { |
| 630 | if ((p1[i] == ' ') || (p1[i] == '-') || (p1[i] == '(') || (p1[i] == ')')) { |
| 631 | i++; |
| 632 | //NSLog(@"Skipped p1 char '%c' at index %d", p1[i], i); |
| 633 | continue; |
| 634 | } |
| 635 | if (p2[j] == '\0') { |
| 636 | //NSLog(@"End of p2 at %d, shorter than p1 at %d", j, i); |
| 637 | areEqual = FALSE; |
| 638 | break; |
| 639 | } |
| 640 | while (p2[j] != '\0') { |
| 641 | if ((p2[j] == ' ') || (p2[j] == '-') || (p2[j] == '(') || (p2[j] == ')')) { |
| 642 | //NSLog(@"Skipped p2 char '%c' at index %d", p2[j], i); |
| 643 | j++; |
| 644 | continue; |
| 645 | } |
| 646 | if(p1[i] != p2[j]) { |
| 647 | areEqual = FALSE; |
| 648 | //NSLog(@"Failed compare of p1[%d] '%c' and p2[%d] '%c'", i, p1[i], j, p2[j]); |
| 649 | break; |
| 650 | } |
| 651 | //NSLog(@"Success compare of p1[%d] '%c' and p2[%d] '%c'", i, p1[i], j, p2[j]); |
| 652 | i++; |
| 653 | j++; |
| 654 | break; |
| 655 | } |
| 656 | if (!areEqual) // we already have a miss |
| 657 | break; |
| 658 | } |
| 659 | if (areEqual) { // we are equal up to end of p1. Check that p2 isn't longer |
| 660 | while (p2[j] != '\0') { |
| 661 | if ((p2[j] == ' ') || (p2[j] == '-') || (p2[j] == '(') || (p2[j] == ')')) { |
| 662 | //NSLog(@"Skipped p2 char '%c' at index %d, after p1 end", p1[i], i); |
| 663 | j++; |
| 664 | continue; |
| 665 | } |
| 666 | //NSLog(@"Extra character(s) for p2 starting at index %d: '%c'", j, p2[j]); |
| 667 | areEqual = FALSE; |
| 668 | break; |
| 669 | } |
| 670 | } |
| 671 | //NSLog(@"Compared %@ and %@, result: %d", aPhone, otherPhone, (int)areEqual); |
| 672 | return areEqual; |
| 673 | } |
| 674 | |
| 675 | #pragma mark ---- cleanup ---- |
| 676 | |
| 677 | - (void)dealloc { |
| 678 | CFRelease(abRecord); |
| 679 | [naptrSelectors release]; |
| 680 | [txtSelectors release]; |
| 681 | [super dealloc]; |
| 682 | } |
| 683 | |
| 684 | @end |
Note: See TracBrowser
for help on using the browser.








