1160 lines
44 KiB
C++
1160 lines
44 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* 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 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#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;
|
|
|
|
namespace {
|
|
|
|
void manageDuplicateHeaders(macabfield **_headerNames, const sal_Int32 _length)
|
|
{
|
|
/* 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...
|
|
OUString newName = CFStringToOUString(static_cast<CFStringRef>(_headerNames[i]->value));
|
|
CFRelease(_headerNames[i]->value);
|
|
newName += " (" + OUString::number(count) + ")";
|
|
_headerNames[i]->value = OUStringToCFString(newName);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
MacabRecords::MacabRecords(const ABAddressBookRef _addressBook, MacabHeader *_header, MacabRecord **_records, sal_Int32 _numRecords)
|
|
: recordsSize(_numRecords), currentRecord(_numRecords), recordType(kABPersonRecordType),
|
|
header(_header), records(_records), addressBook(_addressBook)
|
|
{
|
|
/* 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
|
|
* entirely different values and even (possibly in the future) a different
|
|
* header.
|
|
*/
|
|
MacabRecords::MacabRecords(const MacabRecords *_copy)
|
|
: recordsSize(_copy->recordsSize), currentRecord(0), recordType(kABPersonRecordType),
|
|
header(nullptr), records(new MacabRecord *[recordsSize]), addressBook(_copy->addressBook),
|
|
m_sName(_copy->m_sName)
|
|
{
|
|
/* Variables constructed... */
|
|
bootstrap_CF_types();
|
|
bootstrap_requiredProperties();
|
|
}
|
|
|
|
|
|
MacabRecords::MacabRecords(const ABAddressBookRef _addressBook)
|
|
: recordsSize(0), currentRecord(0), recordType(kABPersonRecordType),
|
|
header(nullptr), records(nullptr), addressBook(_addressBook)
|
|
{
|
|
/* 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 != nullptr)
|
|
{
|
|
sal_Int32 i;
|
|
|
|
for(i = 0; i < recordsSize; i++)
|
|
delete records[i];
|
|
|
|
delete [] records;
|
|
}
|
|
|
|
if(header != nullptr)
|
|
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 = static_cast<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 = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(allRecords, i));
|
|
records[i] = createMacabRecord(record, header, recordType);
|
|
}
|
|
currentRecord = recordsSize;
|
|
|
|
CFRelease(allRecords);
|
|
}
|
|
|
|
|
|
MacabRecords::~MacabRecords()
|
|
{
|
|
}
|
|
|
|
|
|
void MacabRecords::setHeader(MacabHeader *_header)
|
|
{
|
|
if(header != nullptr)
|
|
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 nullptr;
|
|
return records[_location];
|
|
}
|
|
|
|
|
|
macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const sal_Int32 _columnNumber) const
|
|
{
|
|
if(_recordNumber >= recordsSize)
|
|
return nullptr;
|
|
|
|
MacabRecord *record = records[_recordNumber];
|
|
|
|
if(_columnNumber < 0 || _columnNumber >= record->getSize())
|
|
return nullptr;
|
|
|
|
return record->get(_columnNumber);
|
|
}
|
|
|
|
|
|
macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, std::u16string_view _columnName)
|
|
const
|
|
{
|
|
if(header != nullptr)
|
|
{
|
|
sal_Int32 columnNumber = header->getColumnNumber(_columnName);
|
|
if(columnNumber == -1)
|
|
return nullptr;
|
|
|
|
return getField(_recordNumber, columnNumber);
|
|
}
|
|
else
|
|
{
|
|
// error: shouldn't access field with null header!
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
sal_Int32 MacabRecords::getFieldNumber(std::u16string_view _columnName) const
|
|
{
|
|
if(header != nullptr)
|
|
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_CFTypes = {
|
|
{CFNumberGetTypeID(), kABIntegerProperty},
|
|
{CFStringGetTypeID(), kABStringProperty},
|
|
{CFDateGetTypeID(), kABDateProperty},
|
|
{CFArrayGetTypeID(), kABArrayProperty},
|
|
{CFDictionaryGetTypeID(), kABDictionaryProperty},
|
|
{CFDataGetTypeID(), 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()
|
|
{
|
|
requiredProperties = {
|
|
kABTitleProperty, kABFirstNameProperty, kABLastNameProperty, kABOrganizationProperty,
|
|
kABAddressProperty, kABPhoneProperty, 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 macOS 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
|
|
* properties 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 = static_cast<sal_Int32>(CFArrayGetCount(_records));
|
|
sal_Int32 numProperties = static_cast<sal_Int32>(CFArrayGetCount(allProperties));
|
|
sal_Int32 numNonRequiredProperties = numProperties - requiredProperties.size();
|
|
|
|
/* While searching through the properties for required properties, these
|
|
* sal_Bools will keep track of what we have found.
|
|
*/
|
|
auto const bFoundRequiredProperties = std::make_unique<bool[]>(requiredProperties.size());
|
|
|
|
|
|
/* 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 k;
|
|
ABRecordRef record;
|
|
CFStringRef property;
|
|
|
|
|
|
/* Allocate and initialize... */
|
|
nonRequiredProperties = new CFStringRef[numNonRequiredProperties];
|
|
k = 0;
|
|
for(std::vector<CFStringRef>::size_type i = 0; i < requiredProperties.size(); i++)
|
|
bFoundRequiredProperties[i] = false;
|
|
|
|
/* Determine the non-required properties... */
|
|
for(sal_Int32 i = 0; i < numProperties; i++)
|
|
{
|
|
bool bFoundProperty = false;
|
|
property = static_cast<CFStringRef>(CFArrayGetValueAtIndex(allProperties, i));
|
|
for(std::vector<CFStringRef>::size_type j = 0; j < requiredProperties.size(); j++)
|
|
{
|
|
if(CFEqual(property, requiredProperties[j]))
|
|
{
|
|
bFoundProperty = true;
|
|
bFoundRequiredProperties[j] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!bFoundProperty)
|
|
{
|
|
/* 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-required 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(std::vector<CFStringRef>::size_type i = 0; i < requiredProperties.size(); i++)
|
|
{
|
|
if(bFoundRequiredProperties[i])
|
|
{
|
|
/* The order of these matters (we want all address properties
|
|
* before any phone properties, or else things will look weird),
|
|
* so we get all possibilities 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(sal_Int32 j = 0; j < numRecords; j++)
|
|
{
|
|
record = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(_records, j));
|
|
headerDataForProperty = createHeaderForProperty(record,requiredProperties[i],_recordType,true);
|
|
if(headerDataForProperty != nullptr)
|
|
{
|
|
(*lcl_header) += headerDataForProperty;
|
|
delete headerDataForProperty;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Couldn't find a required property...
|
|
OSL_FAIL(OString("MacabRecords::createHeaderForRecordType: could not find required property: " +
|
|
OUStringToOString(CFStringToOUString(requiredProperties[i]), RTL_TEXTENCODING_ASCII_US)).getStr());
|
|
}
|
|
}
|
|
|
|
/* And now, non-required properties... */
|
|
for(sal_Int32 i = 0; i < numRecords; i++)
|
|
{
|
|
record = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(_records, i));
|
|
|
|
for(sal_Int32 j = 0; j < numNonRequiredProperties; j++)
|
|
{
|
|
property = nonRequiredProperties[j];
|
|
headerDataForProperty = createHeaderForProperty(record,property,_recordType,false);
|
|
if(headerDataForProperty != nullptr)
|
|
{
|
|
(*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 bool _isPropertyRequired) const
|
|
{
|
|
// local variables
|
|
CFStringRef localizedPropertyName;
|
|
CFTypeRef propertyValue;
|
|
ABPropertyType propertyType;
|
|
MacabHeader *result;
|
|
|
|
/* Get the property's value */
|
|
propertyValue = ABRecordCopyValue(_record,_propertyName);
|
|
if(propertyValue == nullptr && !_isPropertyRequired)
|
|
return nullptr;
|
|
|
|
propertyType = ABTypeOfProperty(addressBook, _recordType, _propertyName);
|
|
localizedPropertyName = ABCopyLocalizedPropertyOrLabel(_propertyName);
|
|
|
|
result = createHeaderForProperty(propertyType, propertyValue, localizedPropertyName);
|
|
|
|
if(propertyValue != nullptr)
|
|
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 = nullptr;
|
|
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 != nullptr)
|
|
{
|
|
sal_Int32 i;
|
|
|
|
sal_Int32 multiLength = ABMultiValueCount(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)));
|
|
CFStringRef multiLabel, localizedMultiLabel;
|
|
OUString multiLabelString;
|
|
OUString multiPropertyString;
|
|
OUString headerNameString;
|
|
ABPropertyType multiType = static_cast<ABPropertyType>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_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(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)), i);
|
|
localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
|
|
multiLabelString = CFStringToOUString(localizedMultiLabel);
|
|
CFRelease(multiLabel);
|
|
CFRelease(localizedMultiLabel);
|
|
headerNameString = multiPropertyString + ": " + 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 != nullptr)
|
|
{
|
|
sal_Int32 i,j,k;
|
|
|
|
// Total number of multi-array or multi-dictionary elements.
|
|
sal_Int32 multiLengthFirstLevel = ABMultiValueCount(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_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;
|
|
OUString multiLabelString;
|
|
OUString multiPropertyString;
|
|
std::vector<std::unique_ptr<MacabHeader>> multiHeaders;
|
|
ABPropertyType multiType = static_cast<ABPropertyType>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_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(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)), i);
|
|
multiValue = ABMultiValueCopyValueAtIndex(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)), i);
|
|
std::unique_ptr<MacabHeader> hdr;
|
|
if(multiValue && multiLabel)
|
|
{
|
|
localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
|
|
multiLabelString = multiPropertyString + ": " + fixLabel(CFStringToOUString(localizedMultiLabel));
|
|
CFRelease(multiLabel);
|
|
CFRelease(localizedMultiLabel);
|
|
multiLabel = OUStringToCFString(multiLabelString);
|
|
hdr.reset(createHeaderForProperty(multiType, multiValue, multiLabel));
|
|
if (!hdr)
|
|
hdr = std::make_unique<MacabHeader>();
|
|
multiLengthSecondLevel += hdr->getSize();
|
|
}
|
|
else
|
|
{
|
|
hdr = std::make_unique<MacabHeader>();
|
|
}
|
|
if(multiValue)
|
|
CFRelease(multiValue);
|
|
if(multiLabel)
|
|
CFRelease(multiLabel);
|
|
multiHeaders.push_back(std::move(hdr));
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* Dictionary */
|
|
case kABDictionaryProperty:
|
|
/* For non-scalars, we can only get more information if the property
|
|
* actually exists.
|
|
*/
|
|
if(_propertyValue != nullptr)
|
|
{
|
|
/* Assume all keys are strings */
|
|
sal_Int32 numRecords = static_cast<sal_Int32>(CFDictionaryGetCount(static_cast<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;
|
|
OUString dictKeyString, propertyNameString;
|
|
ABPropertyType dictType;
|
|
MacabHeader **dictHeaders = new MacabHeader *[numRecords];
|
|
OUString dictLabelString;
|
|
CFStringRef dictLabel, localizedDictKey;
|
|
|
|
/* Get the keys and values */
|
|
dictKeys = static_cast<CFStringRef *>(malloc(sizeof(CFStringRef)*numRecords));
|
|
dictValues = static_cast<CFTypeRef *>(malloc(sizeof(CFTypeRef)*numRecords));
|
|
CFDictionaryGetKeysAndValues(static_cast<CFDictionaryRef>(_propertyValue), reinterpret_cast<const void **>(dictKeys), 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 = getABTypeFromCFType( CFGetTypeID(dictValues[i]) );
|
|
localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]);
|
|
dictKeyString = CFStringToOUString(localizedDictKey);
|
|
dictLabelString = propertyNameString + ": " + 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 != nullptr)
|
|
{
|
|
sal_Int32 arrLength = static_cast<sal_Int32>(CFArrayGetCount(static_cast<CFArrayRef>(_propertyValue)));
|
|
sal_Int32 i,j,k;
|
|
CFTypeRef arrValue;
|
|
ABPropertyType arrType;
|
|
std::vector<std::unique_ptr<MacabHeader>> arrHeaders;
|
|
OUString propertyNameString = CFStringToOUString(_propertyName);
|
|
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 = CFArrayGetValueAtIndex(static_cast<CFArrayRef>(_propertyValue), i);
|
|
arrType = getABTypeFromCFType( CFGetTypeID(arrValue) );
|
|
arrLabelString = propertyNameString + OUString::number(i);
|
|
arrLabel = OUStringToCFString(arrLabelString);
|
|
auto hdr = std::unique_ptr<MacabHeader>(createHeaderForProperty(arrType, arrValue, arrLabel));
|
|
if (!hdr)
|
|
hdr = std::make_unique<MacabHeader>();
|
|
length += hdr->getSize();
|
|
CFRelease(arrLabel);
|
|
arrHeaders.push_back(std::move(hdr));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
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);
|
|
for(sal_Int32 i = 0; i < length; ++i)
|
|
delete headerNames[i];
|
|
delete [] headerNames;
|
|
return headerResult;
|
|
}
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/* 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 = static_cast<sal_Int32>(CFArrayGetCount(recordProperties));
|
|
|
|
sal_Int32 i;
|
|
|
|
CFTypeRef propertyValue;
|
|
ABPropertyType propertyType;
|
|
|
|
CFStringRef propertyName, localizedPropertyName;
|
|
OUString propertyNameString;
|
|
for(i = 0; i < numProperties; i++)
|
|
{
|
|
propertyName = static_cast<CFStringRef>(CFArrayGetValueAtIndex(recordProperties, i));
|
|
localizedPropertyName = ABCopyLocalizedPropertyOrLabel(propertyName);
|
|
propertyNameString = CFStringToOUString(localizedPropertyName);
|
|
CFRelease(localizedPropertyName);
|
|
|
|
/* Get the property's value */
|
|
propertyValue = ABRecordCopyValue(_abrecord,propertyName);
|
|
if(propertyValue != nullptr)
|
|
{
|
|
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 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 OUString& _propertyName, const CFTypeRef _propertyValue) const
|
|
{
|
|
/* If there is no value, return */
|
|
if(_propertyValue == nullptr)
|
|
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 occurred).
|
|
* 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.
|
|
*/
|
|
bool bPlaced = false;
|
|
OUString columnName = _propertyName;
|
|
sal_Int32 i = 1;
|
|
|
|
// A big safeguard to prevent two fields from having the same name.
|
|
while(!bPlaced)
|
|
{
|
|
sal_Int32 columnNumber = _header->getColumnNumber(columnName);
|
|
bPlaced = true;
|
|
if(columnNumber != -1)
|
|
{
|
|
// collision! A property already exists here!
|
|
if(_abrecord->get(columnNumber) != nullptr)
|
|
{
|
|
bPlaced = false;
|
|
i++;
|
|
columnName = _propertyName + " (" + OUString::number(i) + ")";
|
|
}
|
|
|
|
// 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 = static_cast<sal_Int32>(CFArrayGetCount(static_cast<CFArrayRef>(_propertyValue)));
|
|
sal_Int32 i;
|
|
OUString newPropertyName;
|
|
|
|
/* Going through each element... */
|
|
for(i = 0; i < arrLength; i++)
|
|
{
|
|
const void *arrValue = CFArrayGetValueAtIndex(static_cast<CFArrayRef>(_propertyValue), i);
|
|
newPropertyName = _propertyName + OUString::number(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 = static_cast<sal_Int32>(CFDictionaryGetCount(static_cast<CFDictionaryRef>(_propertyValue)));
|
|
OUString dictKeyString;
|
|
sal_Int32 i;
|
|
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 = static_cast<CFStringRef *>(malloc(sizeof(CFStringRef)*numRecords));
|
|
dictValues = static_cast<CFTypeRef *>(malloc(sizeof(CFTypeRef)*numRecords));
|
|
CFDictionaryGetKeysAndValues(static_cast<CFDictionaryRef>(_propertyValue), reinterpret_cast<const void **>(dictKeys), dictValues);
|
|
|
|
/* Going through each element... */
|
|
for(i = 0; i < numRecords; i++)
|
|
{
|
|
localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]);
|
|
dictKeyString = CFStringToOUString(localizedDictKey);
|
|
CFRelease(localizedDictKey);
|
|
newPropertyName = _propertyName + ": " + 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(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)));
|
|
CFStringRef multiLabel, localizedMultiLabel;
|
|
CFTypeRef multiValue;
|
|
OUString multiLabelString, newPropertyName;
|
|
ABPropertyType multiType = static_cast<ABPropertyType>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue))) - 0x100);
|
|
|
|
/* Go through each element... */
|
|
for(i = 0; i < multiLength; i++)
|
|
{
|
|
/* Label and value */
|
|
multiLabel = ABMultiValueCopyLabelAtIndex(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)), i);
|
|
multiValue = ABMultiValueCopyValueAtIndex(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)), i);
|
|
|
|
localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
|
|
multiLabelString = CFStringToOUString(localizedMultiLabel);
|
|
newPropertyName = _propertyName + ": " + 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
|
|
{
|
|
for(auto const & i: lcl_CFTypes)
|
|
{
|
|
/* A match! */
|
|
if(i.cf == cf_type)
|
|
{
|
|
return static_cast<ABPropertyType>(i.ab);
|
|
}
|
|
}
|
|
return kABErrorInProperty;
|
|
}
|
|
|
|
|
|
sal_Int32 MacabRecords::size() const
|
|
{
|
|
return currentRecord;
|
|
}
|
|
|
|
|
|
MacabRecords *MacabRecords::begin()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
|
|
MacabRecords::iterator::iterator ()
|
|
{
|
|
}
|
|
|
|
|
|
MacabRecords::iterator& MacabRecords::iterator::operator= (MacabRecords *_records)
|
|
{
|
|
id = 0;
|
|
records = _records;
|
|
return *this;
|
|
}
|
|
|
|
|
|
void MacabRecords::iterator::operator++ ()
|
|
{
|
|
id++;
|
|
}
|
|
|
|
|
|
bool MacabRecords::iterator::operator!= (const sal_Int32 i) const
|
|
{
|
|
return(id != i);
|
|
}
|
|
|
|
|
|
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 OUString& _sName)
|
|
{
|
|
m_sName = _sName;
|
|
}
|
|
|
|
|
|
OUString const & MacabRecords::getName() const
|
|
{
|
|
return m_sName;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|