/* NGVCard+SOGo.m - this file is part of SOGo * * Copyright (C) 2009-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #import #import #import #import #import "NSDictionary+LDIF.h" #import "NGVCard+SOGo.h" /* objectclass ( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) ) objectclass ( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an organizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) ) objectclass ( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RFC2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) ) objectclass ( 1.3.6.1.4.1.13769.9.1 NAME 'mozillaAbPersonAlpha' SUP top AUXILIARY MUST ( cn ) MAY( c $ description $ displayName $ facsimileTelephoneNumber $ givenName $ homePhone $ l $ mail $ mobile $ mozillaCustom1 $ mozillaCustom2 $ mozillaCustom3 $ mozillaCustom4 $ mozillaHomeCountryName $ mozillaHomeLocalityName $ mozillaHomePostalCode $ mozillaHomeState $ mozillaHomeStreet $ mozillaHomeStreet2 $ mozillaHomeUrl $ mozillaNickname $ mozillaSecondEmail $ mozillaUseHtmlMail $ mozillaWorkStreet2 $ mozillaWorkUrl $ nsAIMid $ o $ ou $ pager $ postalCode $ postOfficeBox $ sn $ st $ street $ telephoneNumber $ title ) ) additional vcard fields: "vcardCategories" test contact (export from tb): dn:: Y249UHLDqW5vbSBOb20sbWFpbD1hZHIxQGVsZS5jb20= objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson objectclass: mozillaAbPersonAlpha givenName:: UHLDqW5vbQ== sn: Nom cn:: UHLDqW5vbSBOb20= mozillaNickname: Surnom mail: adr1@ele.com mozillaSecondEmail: adralt@ele.com nsAIMid: pseudo aim modifytimestamp: 1324509379 telephoneNumber: travail homePhone: dom facsimiletelephonenumber: fax pager: pager mobile: port mozillaHomeStreet:: YWRyMSBwcml2w6ll mozillaHomeStreet2:: YWRyMiBwcml2w6ll mozillaHomeLocalityName: ville/locallite mozillaHomeState:: w6l0YXQvcHJvdg== mozillaHomePostalCode: codepos mozillaHomeCountryName: pays street: adr1 pro mozillaWorkStreet2: adr2 pro l: ville pro st: etat pro postalCode: codepro c: payspro title: fonction pro ou: service pro o: soc pro mozillaWorkUrl: webpro mozillaHomeUrl: web birthyear: 1946 birthmonth: 12 birthday: 04 mozillaCustom1: d1 mozillaCustom2: d2 mozillaCustom3: d3 mozillaCustom4: d4 description: notes convention: - our "LDIF records" are inetOrgPerson + mozillaAbPersonAlpha + a few custom fields (categories) - all keys are lowercase */ @implementation NGVCard (SOGoExtensions) /* LDIF -> VCARD */ - (CardElement *) elementWithTag: (NSString *) elementTag ofType: (NSString *) type { NSArray *elements; CardElement *element; elements = [self childrenWithTag: elementTag andAttribute: @"type" havingValue: type]; if ([elements count] > 0) element = [elements objectAtIndex: 0]; else { element = [CardElement elementWithTag: elementTag]; [element addType: type]; [self addChild: element]; } return element; } - (void) _setPhoneValues: (NSDictionary *) ldifRecord { CardElement *phone; phone = [self elementWithTag: @"tel" ofType: @"work"]; [phone setSingleValue: [ldifRecord objectForKey: @"telephonenumber"] forKey: @""]; phone = [self elementWithTag: @"tel" ofType: @"home"]; [phone setSingleValue: [ldifRecord objectForKey: @"homephone"] forKey: @""]; phone = [self elementWithTag: @"tel" ofType: @"cell"]; [phone setSingleValue: [ldifRecord objectForKey: @"mobile"] forKey: @""]; phone = [self elementWithTag: @"tel" ofType: @"fax"]; [phone setSingleValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"] forKey: @""]; phone = [self elementWithTag: @"tel" ofType: @"pager"]; [phone setSingleValue: [ldifRecord objectForKey: @"pager"] forKey: @""]; } - (void) _setEmails: (NSDictionary *) ldifRecord { CardElement *mail, *homeMail; mail = [self elementWithTag: @"email" ofType: @"work"]; [mail setSingleValue: [ldifRecord objectForKey: @"mail"] forKey: @""]; homeMail = [self elementWithTag: @"email" ofType: @"home"]; [homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""]; [[self uniqueChildWithTag: @"x-mozilla-html"] setSingleValue: [ldifRecord objectForKey: @"mozillausehtmlmail"] forKey: @""]; } - (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord { NSInteger year, yearOfToday, month, day; CardElement *element; NSCalendarDate *now; NSArray *units; NSString *ou; id o; [self setNWithFamily: [ldifRecord objectForKey: @"sn"] given: [ldifRecord objectForKey: @"givenname"] additional: nil prefixes: nil suffixes: nil]; [self setNickname: [ldifRecord objectForKey: @"mozillanickname"]]; [self setFn: [ldifRecord objectForKey: @"displayname"]]; [self setTitle: [ldifRecord objectForKey: @"title"]]; element = [self elementWithTag: @"adr" ofType: @"home"]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet2"] atIndex: 1 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet"] atIndex: 2 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomelocalityname"] atIndex: 3 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestate"] atIndex: 4 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomepostalcode"] atIndex: 5 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomecountryname"] atIndex: 6 forKey: @""]; element = [self elementWithTag: @"adr" ofType: @"work"]; [element setSingleValue: [ldifRecord objectForKey: @"mozillaworkstreet2"] atIndex: 1 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"street"] atIndex: 2 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"l"] atIndex: 3 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"st"] atIndex: 4 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"postalcode"] atIndex: 5 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"c"] atIndex: 6 forKey: @""]; ou = [ldifRecord objectForKey: @"ou"]; if (ou) units = [NSArray arrayWithObject: ou]; else units = nil; [self setOrg: [ldifRecord objectForKey: @"o"] units: units]; [self _setPhoneValues: ldifRecord]; [self _setEmails: ldifRecord]; [[self elementWithTag: @"url" ofType: @"home"] setSingleValue: [ldifRecord objectForKey: @"mozillahomeurl"] forKey: @""]; [[self elementWithTag: @"url" ofType: @"work"] setSingleValue: [ldifRecord objectForKey: @"mozillaworkurl"] forKey: @""]; [[self uniqueChildWithTag: @"x-aim"] setSingleValue: [ldifRecord objectForKey: @"nsaimid"] forKey: @""]; now = [NSCalendarDate date]; year = [[ldifRecord objectForKey: @"birthyear"] intValue]; if (year < 100) { yearOfToday = [now yearOfCommonEra]; if (year == 0) year = yearOfToday; else if (yearOfToday < (year + 2000)) year += 1900; else year += 2000; } month = [[ldifRecord objectForKey: @"birthmonth"] intValue]; day = [[ldifRecord objectForKey: @"birthday"] intValue]; if (year && month && day) [self setBday: [NSString stringWithFormat: @"%.4d-%.2d-%.2d", year, month, day]]; else [self setBday: @""]; /* hack to carry SOGoLDAPContactInfo to vcards */ [[self uniqueChildWithTag: @"x-sogo-contactinfo"] setSingleValue: [ldifRecord objectForKey: @"c_info"] forKey: @""]; [self setNote: [ldifRecord objectForKey: @"description"]]; o = [ldifRecord objectForKey: @"vcardcategories"]; // We can either have an array (from SOGo's web gui) or a // string (from a LDIF import) as the value here. if ([o isKindOfClass: [NSArray class]]) [self setCategories: o]; else [self setCategories: [o componentsSeparatedByString: @","]]; [self cleanupEmptyChildren]; } /* VCARD -> LDIF */ - (NSString *) _simpleValueForType: (NSString *) aType inArray: (NSArray *) anArray excluding: (NSString *) aTypeToExclude { NSArray *elements; NSString *value; elements = [anArray cardElementsWithAttribute: @"type" havingValue: aType]; value = nil; if ([elements count] > 0) { CardElement *ce; int i; for (i = 0; i < [elements count]; i++) { ce = [elements objectAtIndex: i]; value = [ce flattenedValuesForKey: @""]; if (!aTypeToExclude) break; if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) break; value = nil; } } return value; } - (void) _setValue: (NSString *) key to: (NSString *) aValue inLDIFRecord: (NSMutableDictionary *) ldifRecord { if (!aValue) aValue = @""; [ldifRecord setObject: aValue forKey: key]; } - (void) _setupEmailFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord { NSArray *elements; NSString *workMail, *homeMail, *mail, *secondEmail; NSUInteger max; elements = [self childrenWithTag: @"email"]; max = [elements count]; if (max > 0) { workMail = [self _simpleValueForType: @"work" inArray: elements excluding: nil]; homeMail = [self _simpleValueForType: @"home" inArray: elements excluding: nil]; mail = workMail; if (mail) secondEmail = homeMail; else { secondEmail = nil; mail = homeMail; } if (!mail) { mail = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; if (max > 1) /* we know secondEmail is not set here either... */ secondEmail = [[elements objectAtIndex: 1] flattenedValuesForKey: @""]; } [self _setValue: @"mail" to: mail inLDIFRecord: ldifRecord]; [self _setValue: @"mozillasecondemail" to: secondEmail inLDIFRecord: ldifRecord]; } [self _setValue: @"mozillausehtmlmail" to: [[self uniqueChildWithTag: @"x-mozilla-html"] flattenedValuesForKey: @""] inLDIFRecord: ldifRecord]; } - (void) _setupOrgFieldsInLDIFRecord: (NSMutableDictionary *) ldifRecord { NSMutableArray *orgServices; CardElement *org; NSString *service; NSUInteger count, max; org = [self org]; [self _setValue: @"o" to: [org flattenedValueAtIndex: 0 forKey: @""] inLDIFRecord: ldifRecord]; max = [[org valuesForKey: @""] count]; if (max > 1) { orgServices = [NSMutableArray arrayWithCapacity: max]; for (count = 1; count < max; count++) { service = [org flattenedValueAtIndex: count forKey: @""]; if ([service length] > 0) [orgServices addObject: service]; } [self _setValue: @"ou" to: [orgServices componentsJoinedByString: @", "] inLDIFRecord: ldifRecord]; } } - (NSMutableDictionary *) asLDIFRecord { NSArray *elements, *categories; CardElement *element; NSMutableDictionary *ldifRecord; NSCalendarDate *birthDay; NSString *dn, *stringValue, *stringValue2; ldifRecord = [NSMutableDictionary dictionaryWithCapacity: 32]; [ldifRecord setObject: [NSArray arrayWithObjects: @"top", @"inetOrgPerson", @"mozillaAbPersonAlpha", nil] forKey: @"objectClass"]; element = [self n]; [self _setValue: @"sn" to: [element flattenedValueAtIndex: 0 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"givenname" to: [element flattenedValueAtIndex: 1 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"displayname" to: [self fn] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillanickname" to: [self nickname] inLDIFRecord: ldifRecord]; elements = [self childrenWithTag: @"tel"]; // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX // one if we do see the FAX field BEFORE the WORK number. [self _setValue: @"telephonenumber" to: [self _simpleValueForType: @"work" inArray: elements excluding: @"fax"] inLDIFRecord: ldifRecord]; [self _setValue: @"homephone" to: [self _simpleValueForType: @"home" inArray: elements excluding: @"fax"] inLDIFRecord: ldifRecord]; [self _setValue: @"mobile" to: [self _simpleValueForType: @"cell" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; [self _setValue: @"facsimiletelephonenumber" to: [self _simpleValueForType: @"fax" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; [self _setValue: @"pager" to: [self _simpleValueForType: @"pager" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; // If we don't have a "home" and "work" phone number but // we have a "voice" one defined, we set it to the "work" value // This can happen when we have : // VERSION:2.1 // N:name;surname;;;; // TEL;VOICE;HOME: // TEL;VOICE;WORK: // TEL;PAGER: // TEL;FAX;WORK: // TEL;CELL:514 123 1234 // TEL;VOICE:450 456 6789 // ADR;HOME:;;;;;; // ADR;WORK:;;;;;; // ADR:;;;;;; if ([[ldifRecord objectForKey: @"telephonenumber"] length] == 0 && [[ldifRecord objectForKey: @"homephone"] length] == 0 && [elements count] > 0) [self _setValue: @"telephonenumber" to: [self _simpleValueForType: @"voice" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; [self _setupEmailFieldsInLDIFRecord: ldifRecord]; [self _setValue: @"nsaimid" to: [[self uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""] inLDIFRecord: ldifRecord]; elements = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]; if (elements && [elements count] > 0) { element = [elements objectAtIndex: 0]; [self _setValue: @"mozillaworkstreet2" to: [element flattenedValueAtIndex: 1 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"street" to: [element flattenedValueAtIndex: 2 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"l" to: [element flattenedValueAtIndex: 3 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"st" to: [element flattenedValueAtIndex: 4 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"postalcode" to: [element flattenedValueAtIndex: 5 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"c" to: [element flattenedValueAtIndex: 6 forKey: @""] inLDIFRecord: ldifRecord]; } elements = [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]; if (elements && [elements count] > 0) { element = [elements objectAtIndex: 0]; [self _setValue: @"mozillahomestreet2" to: [element flattenedValueAtIndex: 1 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomestreet" to: [element flattenedValueAtIndex: 2 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomelocalityname" to: [element flattenedValueAtIndex: 3 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomestate" to: [element flattenedValueAtIndex: 4 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomepostalcode" to: [element flattenedValueAtIndex: 5 forKey: @""] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomecountryname" to: [element flattenedValueAtIndex: 6 forKey: @""] inLDIFRecord: ldifRecord]; } elements = [self childrenWithTag: @"url"]; [self _setValue: @"mozillaworkurl" to: [self _simpleValueForType: @"work" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; [self _setValue: @"mozillahomeurl" to: [self _simpleValueForType: @"home" inArray: elements excluding: nil] inLDIFRecord: ldifRecord]; // If we don't have a "work" or "home" URL but we still have // an URL field present, let's add it to the "home" value if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] == 0 && [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && [elements count] > 0) [self _setValue: @"mozillahomeurl" to: [[elements objectAtIndex: 0] flattenedValuesForKey: @""] inLDIFRecord: ldifRecord]; // If we do have a "work" URL but no "home" URL but two // values URLs present, let's add the second one as the home URL else if ([[ldifRecord objectForKey: @"mozillaworkurl"] length] > 0 && [[ldifRecord objectForKey: @"mozillahomeurl"] length] == 0 && [elements count] > 1) { int i; for (i = 0; i < [elements count]; i++) { if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""] caseInsensitiveCompare: [ldifRecord objectForKey: @"mozillaworkurl"]] != NSOrderedSame) { [self _setValue: @"mozillahomeurl" to: [[elements objectAtIndex: i] flattenedValuesForKey: @""] inLDIFRecord: ldifRecord]; break; } } } [self _setValue: @"title" to: [self title] inLDIFRecord: ldifRecord]; [self _setupOrgFieldsInLDIFRecord: ldifRecord]; categories = [self categories]; if ([categories count] > 0) [ldifRecord setValue: categories forKey: @"vcardcategories"]; birthDay = [[self bday] asCalendarDate]; if (birthDay) { stringValue = [NSString stringWithFormat: @"%.4d", [birthDay yearOfCommonEra]]; [self _setValue: @"birthyear" to: stringValue inLDIFRecord: ldifRecord]; stringValue = [NSString stringWithFormat: @"%.2d", [birthDay monthOfYear]]; [self _setValue: @"birthmonth" to: stringValue inLDIFRecord: ldifRecord]; stringValue = [NSString stringWithFormat: @"%.2d", [birthDay dayOfMonth]]; [self _setValue: @"birthday" to: stringValue inLDIFRecord: ldifRecord]; } [self _setValue: @"description" to: [self note] inLDIFRecord: ldifRecord]; stringValue = [ldifRecord objectForKey: @"displayname"]; stringValue2 = [ldifRecord objectForKey: @"mail"]; if ([stringValue length] > 0) { if ([stringValue2 length] > 0) dn = [NSString stringWithFormat: @"cn=%@,mail=%@", stringValue, stringValue2]; else dn = [NSString stringWithFormat: @"cn=%@", stringValue]; } else if ([stringValue2 length] > 0) dn = [NSString stringWithFormat: @"mail=%@", stringValue2]; else dn = @""; [ldifRecord setObject: dn forKey: @"dn"]; return ldifRecord; } - (NSString *) workCompany { CardElement *org; NSString *company; org = [self org]; company = [org flattenedValueAtIndex: 0 forKey: @""]; if ([company length] == 0) company = nil; return company; } - (NSString *) fullName { CardElement *n; NSString *fn, *firstName, *lastName, *org; fn = [self fn]; if ([fn length] == 0) { n = [self n]; lastName = [n flattenedValueAtIndex: 0 forKey: @""]; firstName = [n flattenedValueAtIndex: 1 forKey: @""]; if ([firstName length] > 0) { if ([lastName length] > 0) fn = [NSString stringWithFormat: @"%@ %@", firstName, lastName]; else fn = firstName; } else if ([lastName length] > 0) fn = lastName; else { n = [self org]; org = [n flattenedValueAtIndex: 0 forKey: @""]; fn = org; } } return fn; } - (NSArray *) secondaryEmails { NSMutableArray *emails; NSString *email; int i; emails = [NSMutableArray array]; [emails addObjectsFromArray: [self childrenWithTag: @"email"]]; [emails removeObjectsInArray: [self childrenWithTag: @"email" andAttribute: @"type" havingValue: @"pref"]]; for (i = [emails count]-1; i >= 0; i--) { email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; if ([email caseInsensitiveCompare: [self preferredEMail]] == NSOrderedSame) [emails removeObjectAtIndex: i]; } return emails; } - (NSString *) _phoneOfType: (NSString *) aType excluding: (NSString *) aTypeToExclude { NSArray *elements, *phones; NSString *phone; phones = [self childrenWithTag: @"tel"]; elements = [phones cardElementsWithAttribute: @"type" havingValue: aType]; phone = nil; if ([elements count] > 0) { CardElement *ce; int i; for (i = 0; i < [elements count]; i++) { ce = [elements objectAtIndex: i]; phone = [ce flattenedValuesForKey: @""]; if (!aTypeToExclude) break; if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) break; phone = nil; } } return phone; } - (NSString *) workPhone { // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX // one if we do see the FAX field BEFORE the WORK number. return [self _phoneOfType: @"work" excluding: @"fax"]; } - (NSString *) homePhone { return [self _phoneOfType: @"home" excluding: @"fax"]; } - (NSString *) fax { return [self _phoneOfType: @"fax" excluding: nil]; } - (NSString *) mobile { return [self _phoneOfType: @"cell" excluding: nil]; } - (NSString *) pager { return [self _phoneOfType: @"pager" excluding: nil]; } - (NSCalendarDate *) birthday { NSString *bday, *value; NSCalendarDate *date; bday = [self bday]; date = nil; if ([bday length] > 0) { // Expected format of BDAY is YYYY[-]MM[-]DD value = [bday stringByReplacingString: @"-" withString: @""]; date = [NSCalendarDate dateFromShortDateString: value andShortTimeString: nil inTimeZone: nil]; } return date; } - (NSMutableDictionary *) quickRecordForContainer: (id) theContainer { NSMutableDictionary *fields; CardElement *element; NSString *value; NSArray *v; fields = [NSMutableDictionary dictionaryWithCapacity: 16]; value = [self fn]; if (value) [fields setObject: value forKey: @"c_cn"]; element = [self n]; [fields setObject: [element flattenedValueAtIndex: 0 forKey: @""] forKey: @"c_sn"]; [fields setObject: [element flattenedValueAtIndex: 1 forKey: @""] forKey: @"c_givenName"]; value = [self preferredTel]; if (value) [fields setObject: value forKey: @"c_telephonenumber"]; value = [self preferredEMail]; if (![value isNotNull]) value = @""; [fields setObject: value forKey: @"c_mail"]; element = [self org]; [fields setObject: [element flattenedValueAtIndex: 0 forKey: @""] forKey: @"c_o"]; [fields setObject: [element flattenedValueAtIndex: 1 forKey: @""] forKey: @"c_ou"]; element = [self preferredAdr]; if (element && ![element isVoid]) [fields setObject: [element flattenedValueAtIndex: 3 forKey: @""] forKey: @"c_l"]; value = [[self uniqueChildWithTag: @"X-AIM"] flattenedValuesForKey: @""]; [fields setObject: value forKey: @"c_screenname"]; v = [[self categories] trimmedComponents]; if ([v count] > 0) [fields setObject: [v componentsJoinedByString: @","] forKey: @"c_categories"]; else [fields setObject: [NSNull null] forKey: @"c_categories"]; [fields setObject: @"vcard" forKey: @"c_component"]; return fields; } @end /* NGVCard */