summaryrefslogtreecommitdiff
path: root/connectivity/source/drivers/macab/MacabRecords.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'connectivity/source/drivers/macab/MacabRecords.cxx')
-rwxr-xr-xconnectivity/source/drivers/macab/MacabRecords.cxx1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/connectivity/source/drivers/macab/MacabRecords.cxx b/connectivity/source/drivers/macab/MacabRecords.cxx
new file mode 100755
index 000000000000..173ffa9627d8
--- /dev/null
+++ b/connectivity/source/drivers/macab/MacabRecords.cxx
@@ -0,0 +1,1212 @@
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org 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 Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 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;
+}
+