| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_connectivity.hxx" |
| |
| #include "MacabRecords.hxx" |
| #include "MacabRecord.hxx" |
| #include "MacabHeader.hxx" |
| #include "macabutilities.hxx" |
| |
| #include <premac.h> |
| #include <Carbon/Carbon.h> |
| #include <AddressBook/ABAddressBookC.h> |
| #include <postmac.h> |
| #include <com/sun/star/util/DateTime.hpp> |
| |
| using namespace connectivity::macab; |
| using namespace com::sun::star::util; |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords::MacabRecords(const ABAddressBookRef _addressBook, MacabHeader *_header, MacabRecord **_records, sal_Int32 _numRecords) |
| { |
| /* Variables passed in... */ |
| header = _header; |
| recordsSize = _numRecords; |
| currentRecord = _numRecords; |
| records = _records; |
| addressBook = _addressBook; |
| |
| /* Default variables... */ |
| recordType = kABPersonRecordType; |
| |
| /* Variables constructed... */ |
| bootstrap_CF_types(); |
| bootstrap_requiredProperties(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Creates a MacabRecords from another: copies the length, name, and |
| * address book of the original, but the header or the records themselves. |
| * The idea is that the only reason to copy a MacabRecords is to create |
| * a filtered version of it, which can have the same length (to avoid |
| * resizing) and will work from the same base addressbook, but might have |
| * entirey different values and even (possibly in the future) a different |
| * header. |
| */ |
| MacabRecords::MacabRecords(const MacabRecords *_copy) |
| { |
| /* Variables passed in... */ |
| recordsSize = _copy->recordsSize; |
| addressBook = _copy->addressBook; |
| m_sName = _copy->m_sName; |
| |
| /* Default variables... */ |
| currentRecord = 0; |
| header = NULL; |
| records = new MacabRecord *[recordsSize]; |
| recordType = kABPersonRecordType; |
| |
| /* Variables constructed... */ |
| bootstrap_CF_types(); |
| bootstrap_requiredProperties(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords::MacabRecords(const ABAddressBookRef _addressBook) |
| { |
| /* Variables passed in... */ |
| addressBook = _addressBook; |
| |
| /* Default variables... */ |
| recordsSize = 0; |
| currentRecord = 0; |
| records = NULL; |
| header = NULL; |
| recordType = kABPersonRecordType; |
| |
| /* Variables constructed... */ |
| bootstrap_CF_types(); |
| bootstrap_requiredProperties(); |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::initialize() |
| { |
| |
| /* Make sure everything is NULL before initializing. (We usually just |
| * initialize after we use the constructor that takes only a |
| * MacabAddressBook, so these variables will most likely already be |
| * NULL. |
| */ |
| if(records != NULL) |
| { |
| sal_Int32 i; |
| |
| for(i = 0; i < recordsSize; i++) |
| delete records[i]; |
| |
| delete [] records; |
| } |
| |
| if(header != NULL) |
| delete header; |
| |
| /* We can handle both default record Address Book record types in |
| * this method, though only kABPersonRecordType is ever used. |
| */ |
| CFArrayRef allRecords; |
| if(CFStringCompare(recordType, kABPersonRecordType, 0) == kCFCompareEqualTo) |
| allRecords = ABCopyArrayOfAllPeople(addressBook); |
| else |
| allRecords = ABCopyArrayOfAllGroups(addressBook); |
| |
| ABRecordRef record; |
| sal_Int32 i; |
| recordsSize = (sal_Int32) CFArrayGetCount(allRecords); |
| records = new MacabRecord *[recordsSize]; |
| |
| /* First, we create the header... */ |
| header = createHeaderForRecordType(allRecords, recordType); |
| |
| /* Then, we create each of the records... */ |
| for(i = 0; i < recordsSize; i++) |
| { |
| record = (ABRecordRef) CFArrayGetValueAtIndex(allRecords, i); |
| records[i] = createMacabRecord(record, header, recordType); |
| } |
| currentRecord = recordsSize; |
| |
| CFRelease(allRecords); |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords::~MacabRecords() |
| { |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::setHeader(MacabHeader *_header) |
| { |
| if(header != NULL) |
| delete header; |
| header = _header; |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabHeader *MacabRecords::getHeader() const |
| { |
| return header; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Inserts a MacabRecord at a given location. If there is already a |
| * MacabRecord at that location, return it. |
| */ |
| MacabRecord *MacabRecords::insertRecord(MacabRecord *_newRecord, const sal_Int32 _location) |
| { |
| MacabRecord *oldRecord; |
| |
| /* If the location is greater than the current allocated size of this |
| * MacabRecords, allocate more space. |
| */ |
| if(_location >= recordsSize) |
| { |
| sal_Int32 i; |
| MacabRecord **newRecordsArray = new MacabRecord *[_location+1]; |
| for(i = 0; i < recordsSize; i++) |
| { |
| newRecordsArray[i] = records[i]; |
| } |
| delete [] records; |
| records = newRecordsArray; |
| } |
| |
| /* Remember: currentRecord refers to one above the highest existing |
| * record (i.e., it refers to where to place the next record if a |
| * location is not given). |
| */ |
| if(_location >= currentRecord) |
| currentRecord = _location+1; |
| |
| oldRecord = records[_location]; |
| records[_location] = _newRecord; |
| return oldRecord; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Insert a record at the next available place. */ |
| void MacabRecords::insertRecord(MacabRecord *_newRecord) |
| { |
| insertRecord(_newRecord, currentRecord); |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecord *MacabRecords::getRecord(const sal_Int32 _location) const |
| { |
| if(_location >= recordsSize) |
| return NULL; |
| return records[_location]; |
| } |
| |
| // ------------------------------------------------------------------------- |
| macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const sal_Int32 _columnNumber) const |
| { |
| if(_recordNumber >= recordsSize) |
| return NULL; |
| |
| MacabRecord *record = records[_recordNumber]; |
| |
| if(_columnNumber < 0 || _columnNumber >= record->getSize()) |
| return NULL; |
| |
| return record->get(_columnNumber); |
| } |
| |
| // ------------------------------------------------------------------------- |
| macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const ::rtl::OUString _columnName) const |
| { |
| if(header != NULL) |
| { |
| sal_Int32 columnNumber = header->getColumnNumber(_columnName); |
| if(columnNumber == -1) |
| return NULL; |
| |
| return getField(_recordNumber, columnNumber); |
| } |
| else |
| { |
| // error: shouldn't access field with null header! |
| return NULL; |
| } |
| } |
| |
| // ------------------------------------------------------------------------- |
| sal_Int32 MacabRecords::getFieldNumber(const ::rtl::OUString _columnName) const |
| { |
| if(header != NULL) |
| return header->getColumnNumber(_columnName); |
| else |
| // error: shouldn't access field with null header! |
| return -1; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Create the lcl_CFTypes array -- we need this because there is no |
| * way to get the ABType of an object from the object itself, and the |
| * function ABTypeOfProperty can't handle multiple levels of data |
| * (e.g., it can tell us that "address" is of type |
| * kABDictionaryProperty, but it cannot tell us that all of the keys |
| * and values in the dictionary have type kABStringProperty. On the |
| * other hand, we _can_ get the CFType out of any object. |
| * Unfortunately, all information about CFTypeIDs comes with the |
| * warning that they change between releases, so we build them |
| * ourselves here. (The one that we can't build is for multivalues, |
| * e.g., kABMultiStringProperty. All of these appear to have the |
| * same type: 1, but there is no function that I've found to give |
| * us that dynamically in case that number ever changes. |
| */ |
| void MacabRecords::bootstrap_CF_types() |
| { |
| lcl_CFTypesLength = 6; |
| lcl_CFTypes = new lcl_CFType[lcl_CFTypesLength]; |
| |
| lcl_CFTypes[0].cf = CFNumberGetTypeID(); |
| lcl_CFTypes[0].ab = kABIntegerProperty; |
| |
| lcl_CFTypes[1].cf = CFStringGetTypeID(); |
| lcl_CFTypes[1].ab = kABStringProperty; |
| |
| lcl_CFTypes[2].cf = CFDateGetTypeID(); |
| lcl_CFTypes[2].ab = kABDateProperty; |
| |
| lcl_CFTypes[3].cf = CFArrayGetTypeID(); |
| lcl_CFTypes[3].ab = kABArrayProperty; |
| |
| lcl_CFTypes[4].cf = CFDictionaryGetTypeID(); |
| lcl_CFTypes[4].ab = kABDictionaryProperty; |
| |
| lcl_CFTypes[5].cf = CFDataGetTypeID(); |
| lcl_CFTypes[5].ab = kABDataProperty; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* This is based on the possible fields required in the mail merge template |
| * in sw. If the fields possible there change, it would be optimal to |
| * change these fields as well. |
| */ |
| void MacabRecords::bootstrap_requiredProperties() |
| { |
| numRequiredProperties = 7; |
| requiredProperties = new CFStringRef[numRequiredProperties]; |
| requiredProperties[0] = kABTitleProperty; |
| requiredProperties[1] = kABFirstNameProperty; |
| requiredProperties[2] = kABLastNameProperty; |
| requiredProperties[3] = kABOrganizationProperty; |
| requiredProperties[4] = kABAddressProperty; |
| requiredProperties[5] = kABPhoneProperty; |
| requiredProperties[6] = kABEmailProperty; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Create the header for a given record type and a given array of records. |
| * Because the array of records and the record type are given, if you want |
| * to, you can run this method on the members of a group, or on any other |
| * filtered list of people and get a header relevant to them (e.g., if |
| * they only have home addresses, the work address fields won't show up). |
| */ |
| MacabHeader *MacabRecords::createHeaderForRecordType(const CFArrayRef _records, const CFStringRef _recordType) const |
| { |
| /* We have two types of properties for a given record type, nonrequired |
| * and required. Required properties are ones that will show up whether |
| * or not they are empty. Nonrequired properties will only show up if |
| * at least one record in the set has that property filled. The reason |
| * is that some properties, like the kABTitleProperty are required by |
| * the mail merge wizard (in module sw) but are by default not shown in |
| * the Mac OS X address book, so they would be weeded out at this stage |
| * and not shown if they were not required. |
| * |
| * Note: with the addition of required properties, I am not sure that |
| * this method still works for kABGroupRecordType (since the required |
| * properites are all for kABPersonRecordType). |
| * |
| * Note: required properties are constructed in the method |
| * bootstrap_requiredProperties() (above). |
| */ |
| CFArrayRef allProperties = ABCopyArrayOfPropertiesForRecordType(addressBook, _recordType); |
| CFStringRef *nonRequiredProperties; |
| sal_Int32 numRecords = (sal_Int32) CFArrayGetCount(_records); |
| sal_Int32 numProperties = (sal_Int32) CFArrayGetCount(allProperties); |
| sal_Int32 numNonRequiredProperties = numProperties - numRequiredProperties; |
| |
| /* While searching through the properties for required properties, these |
| * sal_Bools will keep track of what we have found. |
| */ |
| sal_Bool bFoundProperty; |
| sal_Bool bFoundRequiredProperties[numRequiredProperties]; |
| |
| |
| /* We have three MacabHeaders: headerDataForProperty is where we |
| * store the result of createHeaderForProperty(), which return a |
| * MacabHeader for a single property. lcl_header is where we store |
| * the MacabHeader that we are constructing. And, nonRequiredHeader |
| * is where we construct the MacabHeader for non-required properties, |
| * so that we can sort them before adding them to lcl_header. |
| */ |
| MacabHeader *headerDataForProperty; |
| MacabHeader *lcl_header = new MacabHeader(); |
| MacabHeader *nonRequiredHeader = new MacabHeader(); |
| |
| /* Other variables... */ |
| sal_Int32 i, j, k; |
| ABRecordRef record; |
| CFStringRef property; |
| |
| |
| /* Allocate and initialize... */ |
| nonRequiredProperties = new CFStringRef[numNonRequiredProperties]; |
| k = 0; |
| for(i = 0; i < numRequiredProperties; i++) |
| bFoundRequiredProperties[i] = sal_False; |
| |
| /* Determine the non-required properties... */ |
| for(i = 0; i < numProperties; i++) |
| { |
| property = (CFStringRef) CFArrayGetValueAtIndex(allProperties, i); |
| bFoundProperty = sal_False; |
| for(j = 0; j < numRequiredProperties; j++) |
| { |
| if(CFEqual(property, requiredProperties[j])) |
| { |
| bFoundProperty = sal_True; |
| bFoundRequiredProperties[j] = sal_True; |
| break; |
| } |
| } |
| |
| if(bFoundProperty == sal_False) |
| { |
| /* If we have found too many non-required properties */ |
| if(k == numNonRequiredProperties) |
| { |
| k++; // so that the OSL_ENSURE below fails |
| break; |
| } |
| nonRequiredProperties[k] = property; |
| k++; |
| } |
| } |
| |
| // Somehow, we got too many or too few non-requird properties... |
| // Most likely, one of the required properties no longer exists, which |
| // we also test later. |
| OSL_ENSURE(k == numNonRequiredProperties, "MacabRecords::createHeaderForRecordType: Found an unexpected number of non-required properties"); |
| |
| /* Fill the header with required properties first... */ |
| for(i = 0; i < numRequiredProperties; i++) |
| { |
| if(bFoundRequiredProperties[i] == sal_True) |
| { |
| /* The order of these matters (we want all address properties |
| * before any phone properties, or else things will look weird), |
| * so we get all possibilitities for each property, going through |
| * each record, and then go onto the next property. |
| * (Note: the reason that we have to go through all records |
| * in the first place is that properties like address, phone, and |
| * e-mail are multi-value properties with an unknown number of |
| * values. A user could specify thirteen different kinds of |
| * e-mail addresses for one of her or his contacts, and we need to |
| * get all of them. |
| */ |
| for(j = 0; j < numRecords; j++) |
| { |
| record = (ABRecordRef) CFArrayGetValueAtIndex(_records, j); |
| headerDataForProperty = createHeaderForProperty(record,requiredProperties[i],_recordType,sal_True); |
| if(headerDataForProperty != NULL) |
| { |
| (*lcl_header) += headerDataForProperty; |
| delete headerDataForProperty; |
| } |
| } |
| } |
| else |
| { |
| // Couldn't find a required property... |
| OSL_ENSURE(false, ::rtl::OString("MacabRecords::createHeaderForRecordType: could not find required property: ") + |
| ::rtl::OUStringToOString(CFStringToOUString(requiredProperties[i]), RTL_TEXTENCODING_ASCII_US)); |
| } |
| } |
| |
| /* And now, non-required properties... */ |
| for(i = 0; i < numRecords; i++) |
| { |
| record = (ABRecordRef) CFArrayGetValueAtIndex(_records, i); |
| |
| for(j = 0; j < numNonRequiredProperties; j++) |
| { |
| property = nonRequiredProperties[j]; |
| headerDataForProperty = createHeaderForProperty(record,property,_recordType,sal_False); |
| if(headerDataForProperty != NULL) |
| { |
| (*nonRequiredHeader) += headerDataForProperty; |
| delete headerDataForProperty; |
| } |
| } |
| |
| } |
| nonRequiredHeader->sortRecord(); |
| |
| (*lcl_header) += nonRequiredHeader; |
| delete nonRequiredHeader; |
| |
| CFRelease(allProperties); |
| delete [] nonRequiredProperties; |
| |
| return lcl_header; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Create a header for a single property. Basically, this method gets |
| * the property's value and type and then calls another method of |
| * the same name to do the dirty work. |
| */ |
| MacabHeader *MacabRecords::createHeaderForProperty(const ABRecordRef _record, const CFStringRef _propertyName, const CFStringRef _recordType, const sal_Bool _isPropertyRequired) const |
| { |
| // local variables |
| CFStringRef localizedPropertyName; |
| CFTypeRef propertyValue; |
| ABPropertyType propertyType; |
| MacabHeader *result; |
| |
| /* Get the property's value */ |
| propertyValue = ABRecordCopyValue(_record,_propertyName); |
| if(propertyValue == NULL && _isPropertyRequired == sal_False) |
| return NULL; |
| |
| propertyType = ABTypeOfProperty(addressBook, _recordType, _propertyName); |
| localizedPropertyName = ABCopyLocalizedPropertyOrLabel(_propertyName); |
| |
| result = createHeaderForProperty(propertyType, propertyValue, localizedPropertyName); |
| |
| if(propertyValue != NULL) |
| CFRelease(propertyValue); |
| |
| return result; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Create a header for a single property. This method is recursive |
| * because a single property might contain several sub-properties that |
| * we also want to treat singly. |
| */ |
| MacabHeader *MacabRecords::createHeaderForProperty(const ABPropertyType _propertyType, const CFTypeRef _propertyValue, const CFStringRef _propertyName) const |
| { |
| macabfield **headerNames = NULL; |
| sal_Int32 length = 0; |
| |
| switch(_propertyType) |
| { |
| /* Scalars */ |
| case kABStringProperty: |
| case kABRealProperty: |
| case kABIntegerProperty: |
| case kABDateProperty: |
| length = 1; |
| headerNames = new macabfield *[1]; |
| headerNames[0] = new macabfield; |
| headerNames[0]->value = _propertyName; |
| headerNames[0]->type = _propertyType; |
| break; |
| |
| /* Multi-scalars */ |
| case kABMultiIntegerProperty: |
| case kABMultiDateProperty: |
| case kABMultiStringProperty: |
| case kABMultiRealProperty: |
| case kABMultiDataProperty: |
| /* For non-scalars, we can only get more information if the property |
| * actually exists. |
| */ |
| if(_propertyValue != NULL) |
| { |
| sal_Int32 i; |
| |
| sal_Int32 multiLength = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue); |
| CFStringRef multiLabel, localizedMultiLabel; |
| ::rtl::OUString multiLabelString; |
| ::rtl::OUString multiPropertyString; |
| ::rtl::OUString headerNameString; |
| ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100); |
| |
| length = multiLength; |
| headerNames = new macabfield *[multiLength]; |
| multiPropertyString = CFStringToOUString(_propertyName); |
| |
| /* Go through each element, and - since each element is a scalar - |
| * just create a new macabfield for it. |
| */ |
| for(i = 0; i < multiLength; i++) |
| { |
| multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i); |
| localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel); |
| multiLabelString = CFStringToOUString(localizedMultiLabel); |
| CFRelease(multiLabel); |
| CFRelease(localizedMultiLabel); |
| headerNameString = multiPropertyString + ::rtl::OUString::createFromAscii(": ") + fixLabel(multiLabelString); |
| headerNames[i] = new macabfield; |
| headerNames[i]->value = OUStringToCFString(headerNameString); |
| headerNames[i]->type = multiType; |
| } |
| } |
| break; |
| |
| /* Multi-array or dictionary */ |
| case kABMultiArrayProperty: |
| case kABMultiDictionaryProperty: |
| /* For non-scalars, we can only get more information if the property |
| * actually exists. |
| */ |
| if(_propertyValue != NULL) |
| { |
| sal_Int32 i,j,k; |
| |
| // Total number of multi-array or multi-dictionary elements. |
| sal_Int32 multiLengthFirstLevel = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue); |
| |
| /* Total length, including the length of each element (e.g., if |
| * this multi-dictionary contains three dictionaries, and each |
| * dictionary has four elements, this variable will be twelve, |
| * whereas multiLengthFirstLevel will be three. |
| */ |
| sal_Int32 multiLengthSecondLevel = 0; |
| |
| CFStringRef multiLabel, localizedMultiLabel; |
| CFTypeRef multiValue; |
| ::rtl::OUString multiLabelString; |
| ::rtl::OUString multiPropertyString; |
| MacabHeader **multiHeaders = new MacabHeader *[multiLengthFirstLevel]; |
| ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100); |
| |
| multiPropertyString = CFStringToOUString(_propertyName); |
| |
| /* Go through each element - since each element can really |
| * contain anything, we run this method again on each element |
| * and store the resulting MacabHeader (in the multiHeaders |
| * array). Then, all we'll have to do is combine the MacabHeaders |
| * into a single one. |
| */ |
| for(i = 0; i < multiLengthFirstLevel; i++) |
| { |
| /* label */ |
| multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i); |
| multiValue = ABMultiValueCopyValueAtIndex((ABMutableMultiValueRef) _propertyValue, i); |
| if(multiValue && multiLabel) |
| { |
| localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel); |
| multiLabelString = multiPropertyString + ::rtl::OUString::createFromAscii(": ") + fixLabel(CFStringToOUString(localizedMultiLabel)); |
| CFRelease(multiLabel); |
| CFRelease(localizedMultiLabel); |
| multiLabel = OUStringToCFString(multiLabelString); |
| multiHeaders[i] = createHeaderForProperty(multiType, multiValue, multiLabel); |
| if (!multiHeaders[i]) |
| multiHeaders[i] = new MacabHeader(); |
| multiLengthSecondLevel += multiHeaders[i]->getSize(); |
| } |
| else |
| { |
| multiHeaders[i] = new MacabHeader(); |
| } |
| if(multiValue) |
| CFRelease(multiValue); |
| } |
| |
| /* We now have enough information to create our final MacabHeader. |
| * We go through each field of each header and add it to the |
| * headerNames array (which is what is used below to construct |
| * the MacabHeader we return). |
| */ |
| length = multiLengthSecondLevel; |
| headerNames = new macabfield *[multiLengthSecondLevel]; |
| |
| for(i = 0, j = 0, k = 0; i < multiLengthSecondLevel; i++,k++) |
| { |
| while(multiHeaders[j]->getSize() == k) |
| { |
| j++; |
| k = 0; |
| } |
| |
| headerNames[i] = multiHeaders[j]->copy(k); |
| } |
| for(i = 0; i < multiLengthFirstLevel; i++) |
| delete multiHeaders[i]; |
| |
| delete [] multiHeaders; |
| } |
| break; |
| |
| /* Dictionary */ |
| case kABDictionaryProperty: |
| /* For non-scalars, we can only get more information if the property |
| * actually exists. |
| */ |
| if(_propertyValue != NULL) |
| { |
| /* Assume all keys are strings */ |
| sal_Int32 numRecords = (sal_Int32) CFDictionaryGetCount((CFDictionaryRef) _propertyValue); |
| |
| /* The only method for getting info out of a CFDictionary, of both |
| * keys and values, is to all of them all at once, so these |
| * variables will hold them. |
| */ |
| CFStringRef *dictKeys; |
| CFTypeRef *dictValues; |
| |
| sal_Int32 i,j,k; |
| ::rtl::OUString dictKeyString, propertyNameString; |
| ABPropertyType dictType; |
| MacabHeader **dictHeaders = new MacabHeader *[numRecords]; |
| ::rtl::OUString dictLabelString; |
| CFStringRef dictLabel, localizedDictKey; |
| |
| /* Get the keys and values */ |
| dictKeys = (CFStringRef *) malloc(sizeof(CFStringRef)*numRecords); |
| dictValues = (CFTypeRef *) malloc(sizeof(CFTypeRef)*numRecords); |
| CFDictionaryGetKeysAndValues((CFDictionaryRef) _propertyValue, (const void **) dictKeys, (const void **) dictValues); |
| |
| propertyNameString = CFStringToOUString(_propertyName); |
| |
| length = 0; |
| /* Go through each element - assuming that the key is a string but |
| * that the value could be anything. Since the value could be |
| * anything, we can't assume that it is scalar (it could even be |
| * another dictionary), so we attempt to get its type using |
| * the method getABTypeFromCFType and then run this method |
| * recursively on that element, storing the MacabHeader that |
| * results. Then, we just combine all of the MacabHeaders into |
| * one. |
| */ |
| for(i = 0; i < numRecords; i++) |
| { |
| dictType = (ABPropertyType) getABTypeFromCFType( CFGetTypeID(dictValues[i]) ); |
| localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]); |
| dictKeyString = CFStringToOUString(localizedDictKey); |
| dictLabelString = propertyNameString + ::rtl::OUString::createFromAscii(": ") + fixLabel(dictKeyString); |
| dictLabel = OUStringToCFString(dictLabelString); |
| dictHeaders[i] = createHeaderForProperty(dictType, dictValues[i], dictLabel); |
| if (!dictHeaders[i]) |
| dictHeaders[i] = new MacabHeader(); |
| length += dictHeaders[i]->getSize(); |
| CFRelease(dictLabel); |
| CFRelease(localizedDictKey); |
| } |
| |
| /* Combine all of the macabfields in each MacabHeader into the |
| * headerNames array, which (at the end of this method) is used |
| * to create the MacabHeader that is returned. |
| */ |
| headerNames = new macabfield *[length]; |
| for(i = 0, j = 0, k = 0; i < length; i++,k++) |
| { |
| while(dictHeaders[j]->getSize() == k) |
| { |
| j++; |
| k = 0; |
| } |
| |
| headerNames[i] = dictHeaders[j]->copy(k); |
| } |
| |
| for(i = 0; i < numRecords; i++) |
| delete dictHeaders[i]; |
| |
| delete [] dictHeaders; |
| free(dictKeys); |
| free(dictValues); |
| } |
| break; |
| |
| /* Array */ |
| case kABArrayProperty: |
| /* For non-scalars, we can only get more information if the property |
| * actually exists. |
| */ |
| if(_propertyValue != NULL) |
| { |
| sal_Int32 arrLength = (sal_Int32) CFArrayGetCount( (CFArrayRef) _propertyValue); |
| sal_Int32 i,j,k; |
| CFTypeRef arrValue; |
| ABPropertyType arrType; |
| MacabHeader **arrHeaders = new MacabHeader *[arrLength]; |
| ::rtl::OUString propertyNameString = CFStringToOUString(_propertyName); |
| ::rtl::OUString arrLabelString; |
| CFStringRef arrLabel; |
| |
| length = 0; |
| /* Go through each element - since the elements here do not have |
| * unique keys like the ones in dictionaries, we create a unique |
| * key out of the id of the element in the array (the first |
| * element gets a 0 plopped onto the end of it, the second a 1... |
| * As with dictionaries, the elements could be anything, including |
| * another array, so we have to run this method recursively on |
| * each element, storing the resulting MacabHeader into an array, |
| * which we then combine into one MacabHeader that is returned. |
| */ |
| for(i = 0; i < arrLength; i++) |
| { |
| arrValue = (CFTypeRef) CFArrayGetValueAtIndex( (CFArrayRef) _propertyValue, i); |
| arrType = (ABPropertyType) getABTypeFromCFType( CFGetTypeID(arrValue) ); |
| arrLabelString = propertyNameString + ::rtl::OUString::valueOf(i); |
| arrLabel = OUStringToCFString(arrLabelString); |
| arrHeaders[i] = createHeaderForProperty(arrType, arrValue, arrLabel); |
| if (!arrHeaders[i]) |
| arrHeaders[i] = new MacabHeader(); |
| length += arrHeaders[i]->getSize(); |
| CFRelease(arrLabel); |
| } |
| |
| headerNames = new macabfield *[length]; |
| for(i = 0, j = 0, k = 0; i < length; i++,k++) |
| { |
| while(arrHeaders[j]->getSize() == k) |
| { |
| j++; |
| k = 0; |
| } |
| |
| headerNames[i] = arrHeaders[j]->copy(k); |
| } |
| for(i = 0; i < arrLength; i++) |
| delete arrHeaders[i]; |
| |
| delete [] arrHeaders; |
| } |
| break; |
| |
| default: |
| break; |
| |
| } |
| |
| /* If we succeeded at adding elements to the headerNames array, then |
| * length will no longer be 0. If it is, create a new MacabHeader |
| * out of the headerNames (after weeding out duplicate headers), and |
| * then return the result. If the length is still 0, return NULL: we |
| * failed to create a MacabHeader out of this property. |
| */ |
| if(length != 0) |
| { |
| manageDuplicateHeaders(headerNames, length); |
| MacabHeader *headerResult = new MacabHeader(length, headerNames); |
| delete [] headerNames; |
| return headerResult; |
| } |
| else |
| return NULL; |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::manageDuplicateHeaders(macabfield **_headerNames, const sal_Int32 _length) const |
| { |
| /* If we have two cases of, say, phone: home, this makes it: |
| * phone: home (1) |
| * phone: home (2) |
| */ |
| sal_Int32 i, j; |
| sal_Int32 count; |
| for(i = _length-1; i >= 0; i--) |
| { |
| count = 1; |
| for( j = i-1; j >= 0; j--) |
| { |
| if(CFEqual(_headerNames[i]->value, _headerNames[j]->value)) |
| { |
| count++; |
| } |
| } |
| |
| // duplicate! |
| if(count != 1) |
| { |
| // There is probably a better way to do this... |
| ::rtl::OUString newName = CFStringToOUString((CFStringRef) _headerNames[i]->value); |
| CFRelease(_headerNames[i]->value); |
| newName += ::rtl::OUString::createFromAscii(" (") + ::rtl::OUString::valueOf(count) + ::rtl::OUString::createFromAscii(")"); |
| _headerNames[i]->value = OUStringToCFString(newName); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Create a MacabRecord out of an ABRecord, using a given MacabHeader and |
| * the record's type. We go through each property for this record type |
| * then process it much like we processed the header (above), with two |
| * exceptions: if we come upon something not in the header, we ignore it |
| * (it's something we don't want to add), and once we find a corresponding |
| * location in the header, we store the property and the property type in |
| * a macabfield. (For the header, we stored the property type and the name |
| * of the property as a CFString.) |
| */ |
| MacabRecord *MacabRecords::createMacabRecord(const ABRecordRef _abrecord, const MacabHeader *_header, const CFStringRef _recordType) const |
| { |
| /* The new record that we will create... */ |
| MacabRecord *macabRecord = new MacabRecord(_header->getSize()); |
| |
| CFArrayRef recordProperties = ABCopyArrayOfPropertiesForRecordType(addressBook, _recordType); |
| sal_Int32 numProperties = (sal_Int32) CFArrayGetCount(recordProperties); |
| |
| sal_Int32 i; |
| |
| CFTypeRef propertyValue; |
| ABPropertyType propertyType; |
| |
| CFStringRef propertyName, localizedPropertyName; |
| ::rtl::OUString propertyNameString; |
| for(i = 0; i < numProperties; i++) |
| { |
| propertyName = (CFStringRef) CFArrayGetValueAtIndex(recordProperties, i); |
| localizedPropertyName = ABCopyLocalizedPropertyOrLabel(propertyName); |
| propertyNameString = CFStringToOUString(localizedPropertyName); |
| CFRelease(localizedPropertyName); |
| |
| /* Get the property's value */ |
| propertyValue = ABRecordCopyValue(_abrecord,propertyName); |
| if(propertyValue != NULL) |
| { |
| propertyType = ABTypeOfProperty(addressBook, _recordType, propertyName); |
| if(propertyType != kABErrorInProperty) |
| insertPropertyIntoMacabRecord(propertyType, macabRecord, _header, propertyNameString, propertyValue); |
| |
| CFRelease(propertyValue); |
| } |
| } |
| CFRelease(recordProperties); |
| return macabRecord; |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Inserts a given property into a MacabRecord. This method calls another |
| * method by the same name after getting the property type (it only |
| * receives the property value). It is called when we aren't given the |
| * property's type already. |
| */ |
| void MacabRecords::insertPropertyIntoMacabRecord(MacabRecord *_abrecord, const MacabHeader *_header, const ::rtl::OUString _propertyName, const CFTypeRef _propertyValue) const |
| { |
| CFTypeID cf_type = CFGetTypeID(_propertyValue); |
| ABPropertyType ab_type = getABTypeFromCFType( cf_type ); |
| |
| if(ab_type != kABErrorInProperty) |
| insertPropertyIntoMacabRecord(ab_type, _abrecord, _header, _propertyName, _propertyValue); |
| } |
| |
| // ------------------------------------------------------------------------- |
| /* Inserts a given property into a MacabRecord. This method is recursive |
| * because properties can contain many sub-properties. |
| */ |
| void MacabRecords::insertPropertyIntoMacabRecord(const ABPropertyType _propertyType, MacabRecord *_abrecord, const MacabHeader *_header, const ::rtl::OUString _propertyName, const CFTypeRef _propertyValue) const |
| { |
| /* If there is no value, return */ |
| if(_propertyValue == NULL) |
| return; |
| |
| /* The main switch statement */ |
| switch(_propertyType) |
| { |
| /* Scalars */ |
| case kABStringProperty: |
| case kABRealProperty: |
| case kABIntegerProperty: |
| case kABDateProperty: |
| { |
| /* Only scalars actually insert a property into the MacabRecord. |
| * In all other cases, this method is called recursively until a |
| * scalar type, an error, or an unknown type are found. |
| * Because of that, the following checks only occur for this type. |
| * We store whether we have successfully placed this property |
| * into the MacabRecord (or whether an unrecoverable error occured). |
| * Then, we try over and over again to place the property into the |
| * record. There are three possible results: |
| * 1) Success! |
| * 2) There is already a property stored at the column of this name, |
| * in which case we have a duplicate header (see the method |
| * manageDuplicateHeaders()). If that is the case, we add an ID |
| * to the end of the column name in the same format as we do in |
| * manageDuplicateHeaders() and try again. |
| * 3) No column of this name exists in the header. In this case, |
| * there is nothing we can do: we have failed to place this |
| * property into the record. |
| */ |
| sal_Bool bPlaced = sal_False; |
| ::rtl::OUString columnName = ::rtl::OUString(_propertyName); |
| sal_Int32 i = 1; |
| |
| // A big safeguard to prevent two fields from having the same name. |
| while(bPlaced != sal_True) |
| { |
| sal_Int32 columnNumber = _header->getColumnNumber(columnName); |
| bPlaced = sal_True; |
| if(columnNumber != -1) |
| { |
| // collision! A property already exists here! |
| if(_abrecord->get(columnNumber) != NULL) |
| { |
| bPlaced = sal_False; |
| i++; |
| columnName = ::rtl::OUString(_propertyName) + ::rtl::OUString::createFromAscii(" (") + ::rtl::OUString::valueOf(i) + ::rtl::OUString::createFromAscii(")"); |
| } |
| |
| // success! |
| else |
| { |
| _abrecord->insertAtColumn(_propertyValue, _propertyType, columnNumber); |
| } |
| } |
| } |
| } |
| break; |
| |
| /* Array */ |
| case kABArrayProperty: |
| { |
| /* An array is basically just a list of anything, so all we do |
| * is go through the array, and rerun this method recursively |
| * on each element. |
| */ |
| sal_Int32 arrLength = (sal_Int32) CFArrayGetCount( (CFArrayRef) _propertyValue); |
| sal_Int32 i; |
| const void *arrValue; |
| ::rtl::OUString newPropertyName; |
| |
| /* Going through each element... */ |
| for(i = 0; i < arrLength; i++) |
| { |
| arrValue = CFArrayGetValueAtIndex( (CFArrayRef) _propertyValue, i); |
| newPropertyName = _propertyName + ::rtl::OUString::valueOf(i); |
| insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, arrValue); |
| CFRelease(arrValue); |
| } |
| |
| } |
| break; |
| |
| /* Dictionary */ |
| case kABDictionaryProperty: |
| { |
| /* A dictionary is basically a hashmap. Technically, it can |
| * hold any object as a key and any object as a value. |
| * For our case, we assume that the key is a string (so that |
| * we can use the key to get the column name and match it against |
| * the header), but we don't assume anything about the value, so |
| * we run this method recursively (or, rather, we run the version |
| * of this method for when we don't know the object's type) until |
| * we hit a scalar value. |
| */ |
| |
| sal_Int32 numRecords = (sal_Int32) CFDictionaryGetCount((CFDictionaryRef) _propertyValue); |
| ::rtl::OUString dictKeyString; |
| sal_Int32 i; |
| ::rtl::OUString newPropertyName; |
| |
| /* Unfortunately, the only way to get both keys and values out |
| * of a dictionary in Carbon is to get them all at once, so we |
| * do that. |
| */ |
| CFStringRef *dictKeys; |
| CFStringRef localizedDictKey; |
| CFTypeRef *dictValues; |
| dictKeys = (CFStringRef *) malloc(sizeof(CFStringRef)*numRecords); |
| dictValues = (CFTypeRef *) malloc(sizeof(CFTypeRef)*numRecords); |
| CFDictionaryGetKeysAndValues((CFDictionaryRef) _propertyValue, (const void **) dictKeys, (const void **) dictValues); |
| |
| /* Going through each element... */ |
| for(i = 0; i < numRecords; i++) |
| { |
| localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]); |
| dictKeyString = CFStringToOUString(localizedDictKey); |
| CFRelease(localizedDictKey); |
| newPropertyName = _propertyName + ::rtl::OUString::createFromAscii(": ") + fixLabel(dictKeyString); |
| insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, dictValues[i]); |
| } |
| |
| free(dictKeys); |
| free(dictValues); |
| } |
| break; |
| |
| /* Multivalue */ |
| case kABMultiIntegerProperty: |
| case kABMultiDateProperty: |
| case kABMultiStringProperty: |
| case kABMultiRealProperty: |
| case kABMultiDataProperty: |
| case kABMultiDictionaryProperty: |
| case kABMultiArrayProperty: |
| { |
| /* All scalar multivalues are handled in the same way. Each element |
| * is a label and a value. All labels are strings |
| * (kABStringProperty), and all values have the same type |
| * (which is the type of the multivalue minus 255, or as |
| * Carbon's list of property types has it, minus 0x100. |
| * We just get the correct type, then go through each element |
| * and get the label and value and print them in a list. |
| */ |
| |
| sal_Int32 i; |
| sal_Int32 multiLength = ABMultiValueCount((ABMutableMultiValueRef) _propertyValue); |
| CFStringRef multiLabel, localizedMultiLabel; |
| CFTypeRef multiValue; |
| ::rtl::OUString multiLabelString, newPropertyName; |
| ABPropertyType multiType = (ABPropertyType) (ABMultiValuePropertyType((ABMutableMultiValueRef) _propertyValue) - 0x100); |
| |
| /* Go through each element... */ |
| for(i = 0; i < multiLength; i++) |
| { |
| /* Label and value */ |
| multiLabel = ABMultiValueCopyLabelAtIndex((ABMutableMultiValueRef) _propertyValue, i); |
| multiValue = ABMultiValueCopyValueAtIndex((ABMutableMultiValueRef) _propertyValue, i); |
| |
| localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel); |
| multiLabelString = CFStringToOUString(localizedMultiLabel); |
| newPropertyName = _propertyName + ::rtl::OUString::createFromAscii(": ") + fixLabel(multiLabelString); |
| insertPropertyIntoMacabRecord(multiType, _abrecord, _header, newPropertyName, multiValue); |
| |
| /* free our variables */ |
| CFRelease(multiLabel); |
| CFRelease(localizedMultiLabel); |
| CFRelease(multiValue); |
| } |
| } |
| break; |
| |
| /* Unhandled types */ |
| case kABErrorInProperty: |
| case kABDataProperty: |
| default: |
| /* An error, as far as I have seen, only shows up as a type |
| * returned by a function for dictionaries when the dictionary |
| * holds many types of values. Since we do not use that function, |
| * it shouldn't come up. I have yet to see the kABDataProperty, |
| * and I am not sure how to represent it as a string anyway, |
| * since it appears to just be a bunch of bytes. Assumably, if |
| * these bytes made up a string, the type would be |
| * kABStringProperty. I think that this is used when we are not |
| * sure what the type is (e.g., it could be a string or a number). |
| * That being the case, I still don't know how to represent it. |
| * And, default should never come up, since we've exhausted all |
| * of the possible types for ABPropertyType, but... just in case. |
| */ |
| break; |
| } |
| |
| } |
| |
| // ------------------------------------------------------------------------- |
| ABPropertyType MacabRecords::getABTypeFromCFType(const CFTypeID cf_type ) const |
| { |
| sal_Int32 i; |
| for(i = 0; i < lcl_CFTypesLength; i++) |
| { |
| /* A match! */ |
| if(lcl_CFTypes[i].cf == (sal_Int32) cf_type) |
| { |
| return (ABPropertyType) lcl_CFTypes[i].ab; |
| } |
| } |
| return kABErrorInProperty; |
| } |
| |
| // ------------------------------------------------------------------------- |
| sal_Int32 MacabRecords::size() const |
| { |
| return currentRecord; |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords *MacabRecords::begin() |
| { |
| return this; |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords::iterator::iterator () |
| { |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecords::iterator::~iterator () |
| { |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::iterator::operator= (MacabRecords *_records) |
| { |
| id = 0; |
| records = _records; |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::iterator::operator++ () |
| { |
| id++; |
| } |
| |
| // ------------------------------------------------------------------------- |
| sal_Bool MacabRecords::iterator::operator!= (const sal_Int32 i) const |
| { |
| return(id != i); |
| } |
| |
| // ------------------------------------------------------------------------- |
| sal_Bool MacabRecords::iterator::operator== (const sal_Int32 i) const |
| { |
| return(id == i); |
| } |
| |
| // ------------------------------------------------------------------------- |
| MacabRecord *MacabRecords::iterator::operator* () const |
| { |
| return records->getRecord(id); |
| } |
| |
| // ------------------------------------------------------------------------- |
| sal_Int32 MacabRecords::end() const |
| { |
| return currentRecord; |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::swap(const sal_Int32 _id1, const sal_Int32 _id2) |
| { |
| MacabRecord *swapRecord = records[_id1]; |
| |
| records[_id1] = records[_id2]; |
| records[_id2] = swapRecord; |
| } |
| |
| // ------------------------------------------------------------------------- |
| void MacabRecords::setName(const ::rtl::OUString _sName) |
| { |
| m_sName = _sName; |
| } |
| |
| // ------------------------------------------------------------------------- |
| ::rtl::OUString MacabRecords::getName() const |
| { |
| return m_sName; |
| } |
| |