blob: ff8e950d7d5590b464124a2a8dc20a0d1a5b079f [file] [log] [blame]
/**************************************************************
*
* 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;
}