Developer Area

root/apps/iphone/superbook/trunk/Classes/DotTelABMapper.m @ 609

Revision 609, 25.2 kB (checked in by henri, 4 years ago)

Fixed a crashing bug on importing into address book unknown uris such as sip and h323
Also worked around bug in bad usage by addressbook of Home/Work/Other labels

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
75NSString * const DotTelABMapperErrorDomain = @"DotTelABMapperErrorDomain";
76static NSDictionary *naptrSelectors;
77static NSDictionary *txtSelectors;
78static NSDictionary *ccMapper;
79
80@implementation DotTelABMapper
81
82@synthesize updatedProperties;
83@synthesize didSetPhoto;
84
85#pragma mark ---- designated initializer ----
86- (DotTelABMapper *)initWithABRecord:(ABRecordRef)rec {
87        if (rec == NULL) {
88                return NULL;
89        }
90        self = [super init];
91        if (abRecord) {
92                CFRelease(abRecord);
93        }
94        abRecord = CFRetain(rec);
95        if (updatedProperties) {
96                [updatedProperties release];
97        }
98        updatedProperties = [[NSMutableArray array] retain];
99        didSetPhoto = NO;
100       
101        NSDictionary *selectorsMapper = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ABMapperSelectors"
102                                                                                                                                                                                                                           ofType:@"plist"]];
103        txtSelectors    = [[selectorsMapper objectForKey:@"txt"] retain];
104        naptrSelectors  = [[selectorsMapper objectForKey:@"naptr"] retain];
105        ccMapper = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"CountryCodeMapper"
106                                                                                                                                                                                  ofType:@"plist"]];
107        return self;
108}
109
110- (DotTelABMapper *)init {
111        // fail
112        return NULL;
113}
114
115#pragma mark ---- mapping entry methods ----
116
117- (NSError *)setTelName:(NSString *)telname {
118        // Set a new telname or update it if it exists and is different
119        BOOL fieldExists        = NO;
120        BOOL fieldChanged       = NO;
121        CFStringRef telUrlValue;
122        ABMultiValueRef allExistingUrls;
123        ABMutableMultiValueRef allUrls;
124        allExistingUrls = ABRecordCopyValue(abRecord, kABPersonURLProperty);
125        if (!allExistingUrls) { // No URLs, so telname doesn't exist. Make a new property to hold the telname
126                allUrls = ABMultiValueCreateMutable(kABMultiStringPropertyType);
127        } else {                // There are URLs, let's find the telname
128                allUrls = ABMultiValueCreateMutableCopy(allExistingUrls);
129                CFRelease(allExistingUrls);
130                for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
131                        telUrlValue = ABMultiValueCopyValueAtIndex(allUrls, j);
132                        if ([(NSString *)telUrlValue hasSuffix:@".tel"] || [(NSString *)telUrlValue hasSuffix:@".tel/"]) {
133                                fieldExists = YES;
134                                // Found a telname. If it's different, update it
135                                if (![telname isEqualToString:(NSString *)telUrlValue]) {
136                                        ABMultiValueReplaceValueAtIndex(allUrls, (CFStringRef)telname, j);
137                                        fieldChanged = YES;
138                                }
139                                CFRelease(telUrlValue);
140                                break;
141                        }
142                        CFRelease(telUrlValue);
143                }
144        }
145        // If telname doesn't exist, create a new one
146        if (!fieldExists) {
147                ABMultiValueAddValueAndLabel(allUrls, (CFStringRef)telname, CFSTR(".tel"), NULL);
148                fieldChanged = YES;
149        }
150        NSError *err = nil;
151        CFErrorRef cferr = (CFErrorRef)err;
152        if (fieldChanged) {
153                BOOL res = ABRecordSetValue(abRecord, kABPersonURLProperty, allUrls, &cferr);
154                if (res) {
155                        //[updatedProperties addObject:[NSNumber numberWithInt:kABPersonURLProperty]];  // Always show the .tel url
156                }
157        }
158        [updatedProperties addObject:[NSNumber numberWithInt:kABPersonURLProperty]];    // as said above, always show .tel url
159        CFRelease(allUrls);
160
161        return err;
162}
163
164- (NSError *)setField:(id)field {
165        if ([field isKindOfClass:[RecordNaptr class]])
166                return [self setNaptrField:(RecordNaptr *)field];
167        if ([field isKindOfClass:[RecordTxt class]])
168                return [self setTxtField:(RecordTxt *)field];
169        if ([field isKindOfClass:[RecordLoc class]])
170                return [self setLocField:(RecordLoc *)field];
171       
172        // wrong class
173        NSError *err = [NSError errorWithDomain:DotTelABMapperErrorDomain code:kDotTelABMapperUnknownRecordType userInfo:nil];
174        return err;
175}
176
177#pragma mark ---- private naptr/txt/loc dispatch methods ---
178
179- (NSError *)setNaptrField:(RecordNaptr *)field {
180        if ([field isTerminal]) {
181                NSString *primaryType = [[field serviceTypeArray] objectAtIndex:0];
182                SEL parseMethod = NSSelectorFromString([naptrSelectors objectForKey:primaryType]);
183                if (parseMethod == (SEL)0)
184                        return NULL;
185                return ((NSError *)[self performSelector:parseMethod withObject:field]);
186        }
187        return NULL;
188}
189
190- (NSError *)setTxtField:(RecordTxt *)field {
191        if (field.subType == RecordTxtSubTypeKeyword) {
192                NSArray *kv = field.keysAndValues;
193                NSString *primaryType = [kv objectAtIndex:0];
194                SEL parseMethod = NSSelectorFromString([txtSelectors objectForKey:primaryType]);
195                if (parseMethod == (SEL)0)
196                        return NULL;
197                return ((NSError *)[self performSelector:parseMethod withObject:kv]);
198        }
199        return NULL;
200}
201
202- (NSError *)setLocField:(RecordLoc *)field {
203        // Do nothing for now
204        // We could do reverse geoloc and grab the actual address, but it's probably
205        // not what the .tel owner intended
206        return NULL;
207}
208
209#pragma mark ---- private mapping methods ---
210
211- (NSError *)setNaptrNone:(RecordNaptr *)rec {
212        return NULL;
213}
214
215- (NSError *)setNaptrPhoto:(RecordNaptr *)rec {
216        NSError *err = nil;
217        if (didSetPhoto) {
218                // Photo was already set once this run, we won't override
219                return err;
220        }
221        CFErrorRef cferr = (CFErrorRef)err;
222        NSString *photoUrl = [rec uriContent];
223        if (photoUrl == nil) return err;
224        NSData *photoData = [NSData     dataWithContentsOfURL:[NSURL URLWithString:photoUrl]];
225        BOOL res = ABPersonSetImageData(abRecord, (CFDataRef)photoData, &cferr);
226        if (res) {
227                didSetPhoto = YES;
228                return nil;
229        }
230        return err;
231}
232
233- (NSError *)setNaptrPhone:(RecordNaptr *)rec {
234        SETNAPTR_INITIALIZATION;
235       
236        // ==== Begin parsing ====
237        allexisting = ABRecordCopyValue(abRecord, kABPersonPhoneProperty);
238        if (!allexisting) {
239                allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType);
240        } else {
241                allupdated = ABMultiValueCreateMutableCopy(allexisting);
242                CFRelease(allexisting);
243        }
244        abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1];
245        if ([lih isEqualToString:@"x-mobile"]) {
246                abLabel = kABPersonPhoneMobileLabel;
247        } else if ([lih isEqualToString:@"x-main"]) {
248                abLabel = kABPersonPhoneMainLabel;
249        } else if ([lih isEqualToString:@"x-home"]) {
250                abLabel = kABHomeLabel;
251        } else if ([lih isEqualToString:@"x-work"]) {
252                abLabel = kABWorkLabel;
253        } else {
254                abLabel = kABOtherLabel;
255        }
256        // ==== End parsing ====
257       
258        SETNAPTR_ASSIGN(kABPersonPhoneProperty);
259}
260
261- (NSError *)setNaptrFax:(RecordNaptr *)rec {
262        SETNAPTR_INITIALIZATION;
263       
264        // ==== Begin parsing ====
265        allexisting = ABRecordCopyValue(abRecord, kABPersonPhoneProperty);
266        if (!allexisting) {
267                allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType);
268        } else {
269                allupdated = ABMultiValueCreateMutableCopy(allexisting);
270                CFRelease(allexisting);
271        }
272        abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1];
273        if ([lih isEqualToString:@"x-main"]) {
274                abLabel = (CFStringRef)NSLocalizedString(@"Main Fax", @"'Main Fax' label");
275        } else if ([lih isEqualToString:@"x-home"]) {
276                abLabel = kABPersonPhoneHomeFAXLabel;
277        }  else {
278                abLabel = kABPersonPhoneWorkFAXLabel;
279        }
280        // ==== End parsing ====
281       
282        SETNAPTR_ASSIGN(kABPersonPhoneProperty);
283}
284
285- (NSError *)setNaptrEmail:(RecordNaptr *)rec {
286        SETNAPTR_INITIALIZATION;
287       
288        // ==== Begin parsing ====
289        allexisting = ABRecordCopyValue(abRecord, kABPersonEmailProperty);
290        if (!allexisting) {
291                allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType);
292        } else {
293                allupdated = ABMultiValueCreateMutableCopy(allexisting);
294                CFRelease(allexisting);
295        }
296        abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1];
297        if ([lih isEqualToString:@"x-main"]) {
298                abLabel = (CFStringRef)NSLocalizedString(@"main", @"Label 'Main' for contact info");
299        } else if ([lih isEqualToString:@"x-home"]) {
300                abLabel = kABHomeLabel;
301        } else if ([lih isEqualToString:@"x-work"]) {
302                abLabel = kABWorkLabel;
303        } else {
304                abLabel = kABOtherLabel;
305        }
306        // ==== End parsing ====
307       
308        SETNAPTR_ASSIGN(kABPersonEmailProperty);
309}
310
311- (NSError *)setNaptrUrl:(RecordNaptr *)rec {
312        SETNAPTR_INITIALIZATION;
313       
314        // ==== Begin parsing ====
315        allexisting = ABRecordCopyValue(abRecord, kABPersonURLProperty);
316        if (!allexisting) {
317                allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType);
318        } else {
319                allupdated = ABMultiValueCreateMutableCopy(allexisting);
320                CFRelease(allexisting);
321        }
322
323        abValue = (CFStringRef)[rec uriContent];
324        if ([lih isEqualToString:@"x-main"]) {
325                abLabel = kABPersonHomePageLabel;
326        } else if ([lih isEqualToString:@"x-home"]) {
327                abLabel = kABPersonHomePageLabel;
328        } else if ([lih isEqualToString:@"x-work"]) {
329                abLabel = kABWorkLabel;
330        } else {
331                abLabel = kABOtherLabel;
332        }
333        // ==== End parsing ====
334       
335        SETNAPTR_ASSIGN(kABPersonURLProperty);
336}
337
338- (NSError *)setNaptrUrlOther:(RecordNaptr *)rec {
339        SETNAPTR_INITIALIZATION;
340       
341        // ==== Begin parsing ====
342        allexisting = ABRecordCopyValue(abRecord, kABPersonURLProperty);
343        if (!allexisting) {
344                allupdated = ABMultiValueCreateMutable(kABMultiStringPropertyType);
345        } else {
346                allupdated = ABMultiValueCreateMutableCopy(allexisting);
347                CFRelease(allexisting);
348        }
349
350        abValue = (CFStringRef)[rec uriContent];
351
352        // It's impossible to append default labels (such as kABHomeLabel) to a custom string
353        // so we'll forego that and simply use the service itself as the label, discarding x-home and x-work
354        if ([lih isEqualToString:@"x-main"]) {
355                abLabel = (CFStringRef)[service stringByAppendingFormat:@" %@",NSLocalizedString(@"main", @"Label 'Main' for contact info")];
356//      } else if ([lih isEqualToString:@"x-home"]) {
357//              abLabel = (CFStringRef)[service stringByAppendingFormat:@" %@",(NSString *)kABHomeLabel];
358//      } else if ([lih isEqualToString:@"x-work"]) {
359//              abLabel = (CFStringRef)[service stringByAppendingFormat:@" %@",(NSString *)kABWorkLabel];
360        } else {
361                abLabel = (CFStringRef)service;
362        }
363        // ==== End parsing ====
364       
365        SETNAPTR_ASSIGN(kABPersonURLProperty);
366}
367
368- (NSError *)setNaptrIM:(RecordNaptr *)rec {
369        // IM fields are dictionaries, not strings. They need special handling.
370        SETNAPTR_INITIALIZATION;
371
372        CFMutableDictionaryRef anIM = (CFMutableDictionaryRef)[NSMutableDictionary dictionaryWithCapacity:2];   // IM dictionary
373
374        // ==== Begin parsing ====
375        allexisting = ABRecordCopyValue(abRecord, kABPersonInstantMessageProperty);
376        if (!allexisting) {
377                allupdated = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);
378        } else {
379                allupdated = ABMultiValueCreateMutableCopy(allexisting);
380                CFRelease(allexisting);
381        }
382
383        abValue = (CFStringRef)[[[rec uriContent] componentsSeparatedByString:@":"] objectAtIndex:1];
384        if ([lih isEqualToString:@"x-main"]) {
385                abLabel = (CFStringRef)NSLocalizedString(@"main", @"Label 'Main' for contact info");
386        } else if ([lih isEqualToString:@"x-home"]) {
387                abLabel = kABHomeLabel;
388        } else if ([lih isEqualToString:@"x-work"]) {
389                abLabel = kABWorkLabel;
390        } else {
391                abLabel = kABOtherLabel;
392        }
393        NSArray *serviceTokens = [service componentsSeparatedByString:@":"];
394        NSString *servicePostfix;
395        if ([serviceTokens count] == 1) {
396                // this only triggers if the service is a generic "x-im" and would just set servicePostfix = @"x-im"
397                servicePostfix = [serviceTokens objectAtIndex:0];
398        } else {
399                servicePostfix = [serviceTokens objectAtIndex:1];
400        }
401        if ([servicePostfix isEqualToString:@"aim"]) {
402                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceAIM);
403        } else if ([servicePostfix isEqualToString:@"icq"]) {
404                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceICQ);
405        } else if ([servicePostfix isEqualToString:@"ymsgr"]) {
406                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceYahoo);
407        } else if ([servicePostfix isEqualToString:@"msnim"]) {
408                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceMSN);
409        } else if ([servicePostfix isEqualToString:@"xmpp"]) {
410                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, kABPersonInstantMessageServiceJabber);
411        } else if ([servicePostfix isEqualToString:@"gtalk"]) {
412                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, @"GoogleTalk");
413        } else {
414                CFDictionaryAddValue(anIM, kABPersonInstantMessageServiceKey, (CFStringRef)servicePostfix);
415        }
416        CFDictionaryAddValue(anIM, kABPersonInstantMessageUsernameKey, abValue);
417        // ==== End parsing ====
418       
419        // ==== Begin assignment ====
420        // Check if an IM with the same service type and label already exists. If so, we replace it.
421        // This is different behavior than phone numbers and urls (i.e. CFStringRefs)
422        // Note that we only replace the first one of the same type and label
423        CFIndex existingIMServiceIndex = kCFNotFound;
424        for (CFIndex i = 0; i < ABMultiValueGetCount(allupdated); i++) {
425                CFDictionaryRef existingIM = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(allupdated, i);
426                if (existingIM) {
427                        CFStringRef anIMService = CFDictionaryGetValue(anIM, kABPersonInstantMessageServiceKey);
428                        CFStringRef existingIMService = CFDictionaryGetValue(existingIM, kABPersonInstantMessageServiceKey);
429                        if (CFStringCompare(anIMService, existingIMService, 0) == kCFCompareEqualTo) {
430                                // Found a matching IM service type
431                                CFStringRef existingLabel = (CFStringRef)ABMultiValueCopyLabelAtIndex(allupdated, i);
432                                if (existingLabel) {
433                                        if (CFStringCompare(abLabel, existingLabel, 0) == kCFCompareEqualTo) {
434                                                // Found a matching label on top of the matching IM service type
435                                                ABMultiValueReplaceValueAtIndex(allupdated, anIM, i);
436                                                existingIMServiceIndex = i;
437                                                CFRelease(existingLabel);
438                                                CFRelease(existingIM);
439                                                break;
440                                        } else {
441                                                // There was a label but it didn't match, don't replace anything
442                                                CFRelease(existingLabel);
443                                        }
444                                } else {
445                                        // No label, but the service type matches. Let's replace and update the label
446                                        ABMultiValueReplaceValueAtIndex(allupdated, anIM, i);
447                                        ABMultiValueReplaceLabelAtIndex(allupdated, abLabel, i);
448                                        existingIMServiceIndex = i;
449                                        CFRelease(existingIM);
450                                        break;                                 
451                                }
452                        }
453                        // At this point, the IM service type didn't match. Keep looping to look for a possible match
454                        CFRelease(existingIM);
455                }
456        }
457        if (existingIMServiceIndex == kCFNotFound) {
458                // Didn't find a matching IM service type and label
459                ABMultiValueAddValueAndLabel(allupdated, anIM, abLabel, NULL);
460                BOOL res = ABRecordSetValue(abRecord, kABPersonInstantMessageProperty, allupdated, &cferr);
461                if (res) {
462                        [updatedProperties addObject:[NSNumber numberWithInt:kABPersonInstantMessageProperty]];
463                }
464               
465        }
466        CFRelease(allupdated);
467        return NULL;
468}
469
470- (NSError *)setTxtNL:(NSArray *)kv {
471        // Name Parsing
472        // Discard the label
473        /*
474         "s" = "Salutation";    //    e.g. "Mr", "Mrs", etc
475         "fn" = "First Name";   //    e.g. "Adam"
476         "ln" = "Last Name";    //    e.g. "Smith"
477         "nn" = "Nickname";             //    e.g. "Ade"
478         */
479        CFErrorRef err = NULL;
480        NSUInteger i, count = [kv count];
481        for (i = 0; i < count-1; i=i+2) {
482                if (err) {
483                        return (NSError *)err;
484                }
485                NSString *k = [kv objectAtIndex:i];             // key
486                NSString *v = [kv objectAtIndex:i+1];   // value
487               
488                if ([k isEqualToString:@"fn"]) {        // kABPersonFirstNameProperty
489                        if (ABRecordSetValue(abRecord, kABPersonFirstNameProperty, (CFStringRef)v, &err))
490                                [updatedProperties addObject:[NSNumber numberWithInt:kABPersonFirstNameProperty]];
491                        continue;
492                }
493                if ([k isEqualToString:@"ln"]) {        // kABPersonLastNameProperty
494                        if (ABRecordSetValue(abRecord, kABPersonLastNameProperty, (CFStringRef)v, &err))
495                                [updatedProperties addObject:[NSNumber numberWithInt:kABPersonLastNameProperty]];
496                        continue;
497                }
498                if ([k isEqualToString:@"nn"]) {        // kABPersonNicknameProperty
499                        if (ABRecordSetValue(abRecord, kABPersonNicknameProperty, (CFStringRef)v, &err))
500                                [updatedProperties addObject:[NSNumber numberWithInt:kABPersonNicknameProperty]];
501                        continue;
502                }
503                if ([k isEqualToString:@"s"]) {         // kABPersonPrefixProperty
504                        if (ABRecordSetValue(abRecord, kABPersonPrefixProperty, (CFStringRef)v, &err))
505                                [updatedProperties addObject:[NSNumber numberWithInt:kABPersonPrefixProperty]];
506                        continue;
507                }
508        }
509        return NULL;
510}
511
512- (NSError *)setTxtBI:(NSArray *)kv {
513        return NULL;
514}
515
516- (NSError *)setTxtPA:(NSArray *)kv {
517        // Used for both "pa" and "bpa"
518        /*
519         "a1" = "Address Line 1";   //    e.g. "8 Wilfred Street"
520         "a2" = "Address Line 2";   //    e.g. "Victoria"
521         "a3" = "Address Line 3";   //    e.g. "Westminster"
522         "tc" = "City";     //    e.g. "London"
523         "sp" = "State";    //    e.g. "Hampshire"
524         "pc" = "Postal Code";     //    e.g. "SW1E 6PL"
525         "c" = "Country";       //    e.g. "Scotland"
526        */
527       
528        // Create the address dictionary
529        NSMutableDictionary *newAddress = [NSMutableDictionary dictionaryWithCapacity:5];
530        NSString *primaryType  = [kv objectAtIndex:0];
531        // NSString *primaryValue = [kv objectAtIndex:1];       // unused
532
533        // Map the pa and bpa to home and work respectively
534        CFStringRef primaryLabel;
535        if ([primaryType isEqualToString:@"pa"]) {
536                primaryLabel = kABHomeLabel;
537        } else if ([primaryType isEqualToString:@"bpa"]) {
538                primaryLabel = kABWorkLabel;
539        } else {
540                return NULL;
541        }
542
543        // Grab all types and values and build the newAddress
544        NSUInteger i, count = [kv count];
545        NSMutableString *addressStreetFull = nil;
546        for (i = 0; i < count-1; i=i+2) {
547                NSString *k = [kv objectAtIndex:i];             // key
548                NSString *v = [kv objectAtIndex:i+1];   // value
549               
550                // Concatenate the address lines
551                if ([k isEqualToString:@"a1"]) {
552                        if (addressStreetFull) {
553                                // if it exists (it shouldn't), prepend a1
554                                [addressStreetFull insertString:[NSString stringWithFormat:@"%@\n", v] atIndex:0];
555                        } else {
556                                addressStreetFull = [NSMutableString stringWithString:v];
557                        }
558                        continue;
559                }
560                if ([k isEqualToString:@"a2"] || [k isEqualToString:@"a3"]) {
561                        if (addressStreetFull) {
562                                [addressStreetFull appendFormat:@"\n%@", v];
563                        } else {
564                                addressStreetFull = [NSMutableString stringWithString:v];
565                        }
566                        continue;
567                }
568                if ([k isEqualToString:@"tc"]) {        // kABPersonAddressCityKey
569                        [newAddress setObject:v forKey:(NSString *)kABPersonAddressCityKey];
570                        continue;
571                }
572                if ([k isEqualToString:@"sp"]) {        // kABPersonAddressStateKey
573                        [newAddress setObject:v forKey:(NSString *)kABPersonAddressStateKey];
574                        continue;
575                }
576                if ([k isEqualToString:@"pc"]) {        // kABPersonAddressZIPKey
577                        [newAddress setObject:v forKey:(NSString *)kABPersonAddressZIPKey];
578                        continue;
579                }
580                if ([k isEqualToString:@"c"]) { // kABPersonAddressCountryKey
581                        [newAddress setObject:v forKey:(NSString *)kABPersonAddressCountryKey];
582                        NSString *countryCode = [ccMapper objectForKey:v];
583                        if (countryCode) {
584                                [newAddress setObject:countryCode forKey:(NSString *)kABPersonAddressCountryCodeKey];
585                        }
586                        continue;
587                }
588        }
589        // Add the concatenated address line
590        // kABPersonAddressStreetKey
591        if (addressStreetFull) {
592                [newAddress setObject:addressStreetFull forKey:(NSString *)kABPersonAddressStreetKey];
593        }
594       
595        // Now match to an existing address and replace if found
596        // Otherwise create a new one
597               
598        BOOL fieldExists        = NO;
599        BOOL fieldChanged       = NO;
600        ABMultiValueRef existingaddresses;      // All existing addresses
601        ABMutableMultiValueRef addresses;       // All addresses
602        CFDictionaryRef anAddress;                      // An existing address
603        CFStringRef aLabel;                                     // Label for that address
604        existingaddresses = ABRecordCopyValue(abRecord, kABPersonAddressProperty);
605        if (!existingaddresses) {       // No addresses. Make a new property to hold the new address
606                addresses = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);
607        } else {                // There are addresses, let's find the first matching one that has the proper label
608                addresses = ABMultiValueCreateMutableCopy(existingaddresses);
609                CFRelease(existingaddresses);
610                for (CFIndex j = 0; j < ABMultiValueGetCount(addresses); j++) {
611                        anAddress = ABMultiValueCopyValueAtIndex(addresses, j);
612                        aLabel = ABMultiValueCopyLabelAtIndex(addresses, j);
613                        if (CFStringCompare(primaryLabel, aLabel, kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
614                                fieldExists = YES;
615                                // Found a matching address. Replace it without checking if it's the same
616                                // It would be better to check similarity and not replace if equal, but the cost is high
617                                ABMultiValueReplaceValueAtIndex(addresses, newAddress, j);
618                                fieldChanged = YES;
619                        }
620                        CFRelease(anAddress);
621                        if (aLabel)
622                                CFRelease(aLabel);
623                        if (fieldExists)
624                                break;
625                }
626        }
627        // If field doesn't exist, create a new one
628        if (!fieldExists) {
629                ABMultiValueAddValueAndLabel(addresses, (CFDictionaryRef)newAddress, primaryLabel, NULL);
630                fieldChanged = YES;
631        }
632        NSError *err;
633        CFErrorRef cferr = (CFErrorRef)err;
634        if (fieldChanged) {
635                BOOL res = ABRecordSetValue(abRecord, kABPersonAddressProperty, addresses, &cferr);
636                if (res) {
637                        [updatedProperties addObject:[NSNumber numberWithInt:kABPersonAddressProperty]];
638                }
639        }
640        CFRelease(addresses);
641       
642        return NULL;
643}
644
645#pragma mark ---- utility methods ----
646
647- (BOOL)record:(NSString *)aRec isEqualToRecord:(NSString *)otherRec forProperty:(ABPropertyID)property {
648        if (property == kABPersonPhoneProperty)
649                return [self phone:aRec isEqualToPhone:otherRec];
650        return [aRec isEqualToString:otherRec];
651}
652
653- (BOOL)phone:(NSString *)aPhone isEqualToPhone:(NSString *)otherPhone {
654        char *p1 = (char *)[aPhone cStringUsingEncoding:NSUTF8StringEncoding];
655        char *p2 = (char *)[otherPhone cStringUsingEncoding:NSUTF8StringEncoding];
656        BOOL areEqual = TRUE;
657        int i = 0;
658        int j = 0;
659        while ((char)p1[i] != '\0') {
660                if ((p1[i] == ' ') || (p1[i] == '-') || (p1[i] == '(') || (p1[i] == ')')) {
661                        i++;
662                        //NSLog(@"Skipped p1 char '%c' at index %d", p1[i], i);
663                        continue;
664                }
665                if (p2[j] == '\0') {
666                        //NSLog(@"End of p2 at %d, shorter than p1 at %d", j, i);
667                        areEqual = FALSE;
668                        break;
669                }
670                while (p2[j] != '\0') {
671                        if ((p2[j] == ' ') || (p2[j] == '-') || (p2[j] == '(') || (p2[j] == ')')) {
672                                //NSLog(@"Skipped p2 char '%c' at index %d", p2[j], i);
673                                j++;
674                                continue;
675                        }
676                        if(p1[i] != p2[j]) {
677                                areEqual = FALSE;
678                                //NSLog(@"Failed compare of p1[%d] '%c' and p2[%d] '%c'", i, p1[i], j, p2[j]);
679                                break;
680                        }
681                        //NSLog(@"Success compare of p1[%d] '%c' and p2[%d] '%c'", i, p1[i], j, p2[j]);
682                        i++;
683                        j++;
684                        break;
685                }
686                if (!areEqual)  // we already have a miss
687                        break;
688        }
689        if (areEqual) { // we are equal up to end of p1. Check that p2 isn't longer
690                while (p2[j] != '\0') {
691                        if ((p2[j] == ' ') || (p2[j] == '-') || (p2[j] == '(') || (p2[j] == ')')) {
692                                //NSLog(@"Skipped p2 char '%c' at index %d, after p1 end", p1[i], i);
693                                j++;
694                                continue;
695                        }
696                        //NSLog(@"Extra character(s) for p2 starting at index %d: '%c'", j, p2[j]);
697                        areEqual = FALSE;
698                        break;
699                }
700        }
701        //NSLog(@"Compared %@ and %@, result: %d", aPhone, otherPhone, (int)areEqual);
702        return areEqual;
703}
704
705#pragma mark ---- cleanup ----
706
707- (void)dealloc {
708        CFRelease(abRecord);
709        [naptrSelectors release];
710        [txtSelectors release];
711        [super dealloc];
712}
713
714@end
Note: See TracBrowser for help on using the browser.
Telnic
Search This Site
Partners
Neustar
ICANN
Main site | WHOIS | Sell .tel | FAQ | Archived Site | About Telnic | Contact Us