1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
26 #include "MacabRecords.hxx"
27 #include "MacabRecord.hxx"
28 #include "MacabHeader.hxx"
29 #include "macabutilities.hxx"
32 #include <Carbon/Carbon.h>
33 #include <AddressBook/ABAddressBookC.h>
35 #include <com/sun/star/util/DateTime.hpp>
37 using namespace connectivity::macab
;
38 using namespace com::sun::star::util
;
42 void manageDuplicateHeaders(macabfield
**_headerNames
, const sal_Int32 _length
)
44 /* If we have two cases of, say, phone: home, this makes it:
50 for(i
= _length
-1; i
>= 0; i
--)
53 for( j
= i
-1; j
>= 0; j
--)
55 if(CFEqual(_headerNames
[i
]->value
, _headerNames
[j
]->value
))
64 // There is probably a better way to do this...
65 OUString newName
= CFStringToOUString(static_cast<CFStringRef
>(_headerNames
[i
]->value
));
66 CFRelease(_headerNames
[i
]->value
);
67 newName
+= " (" + OUString::number(count
) + ")";
68 _headerNames
[i
]->value
= OUStringToCFString(newName
);
75 MacabRecords::MacabRecords(const ABAddressBookRef _addressBook
, MacabHeader
*_header
, MacabRecord
**_records
, sal_Int32 _numRecords
)
76 : recordsSize(_numRecords
), currentRecord(_numRecords
), recordType(kABPersonRecordType
),
77 header(_header
), records(_records
), addressBook(_addressBook
)
79 /* Variables constructed... */
81 bootstrap_requiredProperties();
85 /* Creates a MacabRecords from another: copies the length, name, and
86 * address book of the original, but the header or the records themselves.
87 * The idea is that the only reason to copy a MacabRecords is to create
88 * a filtered version of it, which can have the same length (to avoid
89 * resizing) and will work from the same base addressbook, but might have
90 * entirely different values and even (possibly in the future) a different
93 MacabRecords::MacabRecords(const MacabRecords
*_copy
)
94 : recordsSize(_copy
->recordsSize
), currentRecord(0), recordType(kABPersonRecordType
),
95 header(nullptr), records(new MacabRecord
*[recordsSize
]), addressBook(_copy
->addressBook
),
96 m_sName(_copy
->m_sName
)
98 /* Variables constructed... */
100 bootstrap_requiredProperties();
104 MacabRecords::MacabRecords(const ABAddressBookRef _addressBook
)
105 : recordsSize(0), currentRecord(0), recordType(kABPersonRecordType
),
106 header(nullptr), records(nullptr), addressBook(_addressBook
)
108 /* Variables constructed... */
109 bootstrap_CF_types();
110 bootstrap_requiredProperties();
114 void MacabRecords::initialize()
117 /* Make sure everything is NULL before initializing. (We usually just
118 * initialize after we use the constructor that takes only a
119 * MacabAddressBook, so these variables will most likely already be
122 if(records
!= nullptr)
126 for(i
= 0; i
< recordsSize
; i
++)
132 if(header
!= nullptr)
135 /* We can handle both default record Address Book record types in
136 * this method, though only kABPersonRecordType is ever used.
138 CFArrayRef allRecords
;
139 if(CFStringCompare(recordType
, kABPersonRecordType
, 0) == kCFCompareEqualTo
)
140 allRecords
= ABCopyArrayOfAllPeople(addressBook
);
142 allRecords
= ABCopyArrayOfAllGroups(addressBook
);
146 recordsSize
= static_cast<sal_Int32
>(CFArrayGetCount(allRecords
));
147 records
= new MacabRecord
*[recordsSize
];
149 /* First, we create the header... */
150 header
= createHeaderForRecordType(allRecords
, recordType
);
152 /* Then, we create each of the records... */
153 for(i
= 0; i
< recordsSize
; i
++)
155 record
= const_cast<ABRecordRef
>(CFArrayGetValueAtIndex(allRecords
, i
));
156 records
[i
] = createMacabRecord(record
, header
, recordType
);
158 currentRecord
= recordsSize
;
160 CFRelease(allRecords
);
164 MacabRecords::~MacabRecords()
169 void MacabRecords::setHeader(MacabHeader
*_header
)
171 if(header
!= nullptr)
177 MacabHeader
*MacabRecords::getHeader() const
183 /* Inserts a MacabRecord at a given location. If there is already a
184 * MacabRecord at that location, return it.
186 MacabRecord
*MacabRecords::insertRecord(MacabRecord
*_newRecord
, const sal_Int32 _location
)
188 MacabRecord
*oldRecord
;
190 /* If the location is greater than the current allocated size of this
191 * MacabRecords, allocate more space.
193 if(_location
>= recordsSize
)
196 MacabRecord
**newRecordsArray
= new MacabRecord
*[_location
+1];
197 for(i
= 0; i
< recordsSize
; i
++)
199 newRecordsArray
[i
] = records
[i
];
202 records
= newRecordsArray
;
205 /* Remember: currentRecord refers to one above the highest existing
206 * record (i.e., it refers to where to place the next record if a
207 * location is not given).
209 if(_location
>= currentRecord
)
210 currentRecord
= _location
+1;
212 oldRecord
= records
[_location
];
213 records
[_location
] = _newRecord
;
218 /* Insert a record at the next available place. */
219 void MacabRecords::insertRecord(MacabRecord
*_newRecord
)
221 insertRecord(_newRecord
, currentRecord
);
225 MacabRecord
*MacabRecords::getRecord(const sal_Int32 _location
) const
227 if(_location
>= recordsSize
)
229 return records
[_location
];
233 macabfield
*MacabRecords::getField(const sal_Int32 _recordNumber
, const sal_Int32 _columnNumber
) const
235 if(_recordNumber
>= recordsSize
)
238 MacabRecord
*record
= records
[_recordNumber
];
240 if(_columnNumber
< 0 || _columnNumber
>= record
->getSize())
243 return record
->get(_columnNumber
);
247 macabfield
*MacabRecords::getField(const sal_Int32 _recordNumber
, std::u16string_view _columnName
)
250 if(header
!= nullptr)
252 sal_Int32 columnNumber
= header
->getColumnNumber(_columnName
);
253 if(columnNumber
== -1)
256 return getField(_recordNumber
, columnNumber
);
260 // error: shouldn't access field with null header!
266 sal_Int32
MacabRecords::getFieldNumber(std::u16string_view _columnName
) const
268 if(header
!= nullptr)
269 return header
->getColumnNumber(_columnName
);
271 // error: shouldn't access field with null header!
276 /* Create the lcl_CFTypes array -- we need this because there is no
277 * way to get the ABType of an object from the object itself, and the
278 * function ABTypeOfProperty can't handle multiple levels of data
279 * (e.g., it can tell us that "address" is of type
280 * kABDictionaryProperty, but it cannot tell us that all of the keys
281 * and values in the dictionary have type kABStringProperty. On the
282 * other hand, we _can_ get the CFType out of any object.
283 * Unfortunately, all information about CFTypeIDs comes with the
284 * warning that they change between releases, so we build them
285 * ourselves here. (The one that we can't build is for multivalues,
286 * e.g., kABMultiStringProperty. All of these appear to have the
287 * same type: 1, but there is no function that I've found to give
288 * us that dynamically in case that number ever changes.
290 void MacabRecords::bootstrap_CF_types()
293 {CFNumberGetTypeID(), kABIntegerProperty
},
294 {CFStringGetTypeID(), kABStringProperty
},
295 {CFDateGetTypeID(), kABDateProperty
},
296 {CFArrayGetTypeID(), kABArrayProperty
},
297 {CFDictionaryGetTypeID(), kABDictionaryProperty
},
298 {CFDataGetTypeID(), kABDataProperty
}};
302 /* This is based on the possible fields required in the mail merge template
303 * in sw. If the fields possible there change, it would be optimal to
304 * change these fields as well.
306 void MacabRecords::bootstrap_requiredProperties()
308 requiredProperties
= {
309 kABTitleProperty
, kABFirstNameProperty
, kABLastNameProperty
, kABOrganizationProperty
,
310 kABAddressProperty
, kABPhoneProperty
, kABEmailProperty
};
314 /* Create the header for a given record type and a given array of records.
315 * Because the array of records and the record type are given, if you want
316 * to, you can run this method on the members of a group, or on any other
317 * filtered list of people and get a header relevant to them (e.g., if
318 * they only have home addresses, the work address fields won't show up).
320 MacabHeader
*MacabRecords::createHeaderForRecordType(const CFArrayRef _records
, const CFStringRef _recordType
) const
322 /* We have two types of properties for a given record type, nonrequired
323 * and required. Required properties are ones that will show up whether
324 * or not they are empty. Nonrequired properties will only show up if
325 * at least one record in the set has that property filled. The reason
326 * is that some properties, like the kABTitleProperty are required by
327 * the mail merge wizard (in module sw) but are by default not shown in
328 * the macOS address book, so they would be weeded out at this stage
329 * and not shown if they were not required.
331 * Note: with the addition of required properties, I am not sure that
332 * this method still works for kABGroupRecordType (since the required
333 * properties are all for kABPersonRecordType).
335 * Note: required properties are constructed in the method
336 * bootstrap_requiredProperties() (above).
338 CFArrayRef allProperties
= ABCopyArrayOfPropertiesForRecordType(addressBook
, _recordType
);
339 CFStringRef
*nonRequiredProperties
;
340 sal_Int32 numRecords
= static_cast<sal_Int32
>(CFArrayGetCount(_records
));
341 sal_Int32 numProperties
= static_cast<sal_Int32
>(CFArrayGetCount(allProperties
));
342 sal_Int32 numNonRequiredProperties
= numProperties
- requiredProperties
.size();
344 /* While searching through the properties for required properties, these
345 * sal_Bools will keep track of what we have found.
347 auto const bFoundRequiredProperties
= std::make_unique
<bool[]>(requiredProperties
.size());
350 /* We have three MacabHeaders: headerDataForProperty is where we
351 * store the result of createHeaderForProperty(), which return a
352 * MacabHeader for a single property. lcl_header is where we store
353 * the MacabHeader that we are constructing. And, nonRequiredHeader
354 * is where we construct the MacabHeader for non-required properties,
355 * so that we can sort them before adding them to lcl_header.
357 MacabHeader
*headerDataForProperty
;
358 MacabHeader
*lcl_header
= new MacabHeader();
359 MacabHeader
*nonRequiredHeader
= new MacabHeader();
361 /* Other variables... */
364 CFStringRef property
;
367 /* Allocate and initialize... */
368 nonRequiredProperties
= new CFStringRef
[numNonRequiredProperties
];
370 for(std::vector
<CFStringRef
>::size_type i
= 0; i
< requiredProperties
.size(); i
++)
371 bFoundRequiredProperties
[i
] = false;
373 /* Determine the non-required properties... */
374 for(sal_Int32 i
= 0; i
< numProperties
; i
++)
376 bool bFoundProperty
= false;
377 property
= static_cast<CFStringRef
>(CFArrayGetValueAtIndex(allProperties
, i
));
378 for(std::vector
<CFStringRef
>::size_type j
= 0; j
< requiredProperties
.size(); j
++)
380 if(CFEqual(property
, requiredProperties
[j
]))
382 bFoundProperty
= true;
383 bFoundRequiredProperties
[j
] = true;
390 /* If we have found too many non-required properties */
391 if(k
== numNonRequiredProperties
)
393 k
++; // so that the OSL_ENSURE below fails
396 nonRequiredProperties
[k
] = property
;
401 // Somehow, we got too many or too few non-required properties...
402 // Most likely, one of the required properties no longer exists, which
403 // we also test later.
404 OSL_ENSURE(k
== numNonRequiredProperties
, "MacabRecords::createHeaderForRecordType: Found an unexpected number of non-required properties");
406 /* Fill the header with required properties first... */
407 for(std::vector
<CFStringRef
>::size_type i
= 0; i
< requiredProperties
.size(); i
++)
409 if(bFoundRequiredProperties
[i
])
411 /* The order of these matters (we want all address properties
412 * before any phone properties, or else things will look weird),
413 * so we get all possibilities for each property, going through
414 * each record, and then go onto the next property.
415 * (Note: the reason that we have to go through all records
416 * in the first place is that properties like address, phone, and
417 * e-mail are multi-value properties with an unknown number of
418 * values. A user could specify thirteen different kinds of
419 * e-mail addresses for one of her or his contacts, and we need to
422 for(sal_Int32 j
= 0; j
< numRecords
; j
++)
424 record
= const_cast<ABRecordRef
>(CFArrayGetValueAtIndex(_records
, j
));
425 headerDataForProperty
= createHeaderForProperty(record
,requiredProperties
[i
],_recordType
,true);
426 if(headerDataForProperty
!= nullptr)
428 (*lcl_header
) += headerDataForProperty
;
429 delete headerDataForProperty
;
435 // Couldn't find a required property...
436 OSL_FAIL(OString("MacabRecords::createHeaderForRecordType: could not find required property: " +
437 OUStringToOString(CFStringToOUString(requiredProperties
[i
]), RTL_TEXTENCODING_ASCII_US
)).getStr());
441 /* And now, non-required properties... */
442 for(sal_Int32 i
= 0; i
< numRecords
; i
++)
444 record
= const_cast<ABRecordRef
>(CFArrayGetValueAtIndex(_records
, i
));
446 for(sal_Int32 j
= 0; j
< numNonRequiredProperties
; j
++)
448 property
= nonRequiredProperties
[j
];
449 headerDataForProperty
= createHeaderForProperty(record
,property
,_recordType
,false);
450 if(headerDataForProperty
!= nullptr)
452 (*nonRequiredHeader
) += headerDataForProperty
;
453 delete headerDataForProperty
;
458 nonRequiredHeader
->sortRecord();
460 (*lcl_header
) += nonRequiredHeader
;
461 delete nonRequiredHeader
;
463 CFRelease(allProperties
);
464 delete [] nonRequiredProperties
;
470 /* Create a header for a single property. Basically, this method gets
471 * the property's value and type and then calls another method of
472 * the same name to do the dirty work.
474 MacabHeader
*MacabRecords::createHeaderForProperty(const ABRecordRef _record
, const CFStringRef _propertyName
, const CFStringRef _recordType
, const bool _isPropertyRequired
) const
477 CFStringRef localizedPropertyName
;
478 CFTypeRef propertyValue
;
479 ABPropertyType propertyType
;
482 /* Get the property's value */
483 propertyValue
= ABRecordCopyValue(_record
,_propertyName
);
484 if(propertyValue
== nullptr && !_isPropertyRequired
)
487 propertyType
= ABTypeOfProperty(addressBook
, _recordType
, _propertyName
);
488 localizedPropertyName
= ABCopyLocalizedPropertyOrLabel(_propertyName
);
490 result
= createHeaderForProperty(propertyType
, propertyValue
, localizedPropertyName
);
492 if(propertyValue
!= nullptr)
493 CFRelease(propertyValue
);
499 /* Create a header for a single property. This method is recursive
500 * because a single property might contain several sub-properties that
501 * we also want to treat singly.
503 MacabHeader
*MacabRecords::createHeaderForProperty(const ABPropertyType _propertyType
, const CFTypeRef _propertyValue
, const CFStringRef _propertyName
) const
505 macabfield
**headerNames
= nullptr;
506 sal_Int32 length
= 0;
508 switch(_propertyType
)
511 case kABStringProperty
:
512 case kABRealProperty
:
513 case kABIntegerProperty
:
514 case kABDateProperty
:
516 headerNames
= new macabfield
*[1];
517 headerNames
[0] = new macabfield
;
518 headerNames
[0]->value
= _propertyName
;
519 headerNames
[0]->type
= _propertyType
;
523 case kABMultiIntegerProperty
:
524 case kABMultiDateProperty
:
525 case kABMultiStringProperty
:
526 case kABMultiRealProperty
:
527 case kABMultiDataProperty
:
528 /* For non-scalars, we can only get more information if the property
531 if(_propertyValue
!= nullptr)
535 sal_Int32 multiLength
= ABMultiValueCount(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)));
536 CFStringRef multiLabel
, localizedMultiLabel
;
537 OUString multiLabelString
;
538 OUString multiPropertyString
;
539 OUString headerNameString
;
540 ABPropertyType multiType
= static_cast<ABPropertyType
>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
))) - 0x100);
542 length
= multiLength
;
543 headerNames
= new macabfield
*[multiLength
];
544 multiPropertyString
= CFStringToOUString(_propertyName
);
546 /* Go through each element, and - since each element is a scalar -
547 * just create a new macabfield for it.
549 for(i
= 0; i
< multiLength
; i
++)
551 multiLabel
= ABMultiValueCopyLabelAtIndex(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)), i
);
552 localizedMultiLabel
= ABCopyLocalizedPropertyOrLabel(multiLabel
);
553 multiLabelString
= CFStringToOUString(localizedMultiLabel
);
554 CFRelease(multiLabel
);
555 CFRelease(localizedMultiLabel
);
556 headerNameString
= multiPropertyString
+ ": " + fixLabel(multiLabelString
);
557 headerNames
[i
] = new macabfield
;
558 headerNames
[i
]->value
= OUStringToCFString(headerNameString
);
559 headerNames
[i
]->type
= multiType
;
564 /* Multi-array or dictionary */
565 case kABMultiArrayProperty
:
566 case kABMultiDictionaryProperty
:
567 /* For non-scalars, we can only get more information if the property
570 if(_propertyValue
!= nullptr)
574 // Total number of multi-array or multi-dictionary elements.
575 sal_Int32 multiLengthFirstLevel
= ABMultiValueCount(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)));
577 /* Total length, including the length of each element (e.g., if
578 * this multi-dictionary contains three dictionaries, and each
579 * dictionary has four elements, this variable will be twelve,
580 * whereas multiLengthFirstLevel will be three.
582 sal_Int32 multiLengthSecondLevel
= 0;
584 CFStringRef multiLabel
, localizedMultiLabel
;
585 CFTypeRef multiValue
;
586 OUString multiLabelString
;
587 OUString multiPropertyString
;
588 std::vector
<std::unique_ptr
<MacabHeader
>> multiHeaders
;
589 ABPropertyType multiType
= static_cast<ABPropertyType
>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
))) - 0x100);
591 multiPropertyString
= CFStringToOUString(_propertyName
);
593 /* Go through each element - since each element can really
594 * contain anything, we run this method again on each element
595 * and store the resulting MacabHeader (in the multiHeaders
596 * array). Then, all we'll have to do is combine the MacabHeaders
599 for(i
= 0; i
< multiLengthFirstLevel
; i
++)
602 multiLabel
= ABMultiValueCopyLabelAtIndex(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)), i
);
603 multiValue
= ABMultiValueCopyValueAtIndex(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)), i
);
604 std::unique_ptr
<MacabHeader
> hdr
;
605 if(multiValue
&& multiLabel
)
607 localizedMultiLabel
= ABCopyLocalizedPropertyOrLabel(multiLabel
);
608 multiLabelString
= multiPropertyString
+ ": " + fixLabel(CFStringToOUString(localizedMultiLabel
));
609 CFRelease(multiLabel
);
610 CFRelease(localizedMultiLabel
);
611 multiLabel
= OUStringToCFString(multiLabelString
);
612 hdr
.reset(createHeaderForProperty(multiType
, multiValue
, multiLabel
));
614 hdr
= std::make_unique
<MacabHeader
>();
615 multiLengthSecondLevel
+= hdr
->getSize();
619 hdr
= std::make_unique
<MacabHeader
>();
622 CFRelease(multiValue
);
624 CFRelease(multiLabel
);
625 multiHeaders
.push_back(std::move(hdr
));
628 /* We now have enough information to create our final MacabHeader.
629 * We go through each field of each header and add it to the
630 * headerNames array (which is what is used below to construct
631 * the MacabHeader we return).
633 length
= multiLengthSecondLevel
;
634 headerNames
= new macabfield
*[multiLengthSecondLevel
];
636 for(i
= 0, j
= 0, k
= 0; i
< multiLengthSecondLevel
; i
++,k
++)
638 while(multiHeaders
[j
]->getSize() == k
)
644 headerNames
[i
] = multiHeaders
[j
]->copy(k
);
650 case kABDictionaryProperty
:
651 /* For non-scalars, we can only get more information if the property
654 if(_propertyValue
!= nullptr)
656 /* Assume all keys are strings */
657 sal_Int32 numRecords
= static_cast<sal_Int32
>(CFDictionaryGetCount(static_cast<CFDictionaryRef
>(_propertyValue
)));
659 /* The only method for getting info out of a CFDictionary, of both
660 * keys and values, is to all of them all at once, so these
661 * variables will hold them.
663 CFStringRef
*dictKeys
;
664 CFTypeRef
*dictValues
;
667 OUString dictKeyString
, propertyNameString
;
668 ABPropertyType dictType
;
669 MacabHeader
**dictHeaders
= new MacabHeader
*[numRecords
];
670 OUString dictLabelString
;
671 CFStringRef dictLabel
, localizedDictKey
;
673 /* Get the keys and values */
674 dictKeys
= static_cast<CFStringRef
*>(malloc(sizeof(CFStringRef
)*numRecords
));
675 dictValues
= static_cast<CFTypeRef
*>(malloc(sizeof(CFTypeRef
)*numRecords
));
676 CFDictionaryGetKeysAndValues(static_cast<CFDictionaryRef
>(_propertyValue
), reinterpret_cast<const void **>(dictKeys
), dictValues
);
678 propertyNameString
= CFStringToOUString(_propertyName
);
681 /* Go through each element - assuming that the key is a string but
682 * that the value could be anything. Since the value could be
683 * anything, we can't assume that it is scalar (it could even be
684 * another dictionary), so we attempt to get its type using
685 * the method getABTypeFromCFType and then run this method
686 * recursively on that element, storing the MacabHeader that
687 * results. Then, we just combine all of the MacabHeaders into
690 for(i
= 0; i
< numRecords
; i
++)
692 dictType
= getABTypeFromCFType( CFGetTypeID(dictValues
[i
]) );
693 localizedDictKey
= ABCopyLocalizedPropertyOrLabel(dictKeys
[i
]);
694 dictKeyString
= CFStringToOUString(localizedDictKey
);
695 dictLabelString
= propertyNameString
+ ": " + fixLabel(dictKeyString
);
696 dictLabel
= OUStringToCFString(dictLabelString
);
697 dictHeaders
[i
] = createHeaderForProperty(dictType
, dictValues
[i
], dictLabel
);
699 dictHeaders
[i
] = new MacabHeader();
700 length
+= dictHeaders
[i
]->getSize();
701 CFRelease(dictLabel
);
702 CFRelease(localizedDictKey
);
705 /* Combine all of the macabfields in each MacabHeader into the
706 * headerNames array, which (at the end of this method) is used
707 * to create the MacabHeader that is returned.
709 headerNames
= new macabfield
*[length
];
710 for(i
= 0, j
= 0, k
= 0; i
< length
; i
++,k
++)
712 while(dictHeaders
[j
]->getSize() == k
)
718 headerNames
[i
] = dictHeaders
[j
]->copy(k
);
721 for(i
= 0; i
< numRecords
; i
++)
722 delete dictHeaders
[i
];
724 delete [] dictHeaders
;
731 case kABArrayProperty
:
732 /* For non-scalars, we can only get more information if the property
735 if(_propertyValue
!= nullptr)
737 sal_Int32 arrLength
= static_cast<sal_Int32
>(CFArrayGetCount(static_cast<CFArrayRef
>(_propertyValue
)));
740 ABPropertyType arrType
;
741 std::vector
<std::unique_ptr
<MacabHeader
>> arrHeaders
;
742 OUString propertyNameString
= CFStringToOUString(_propertyName
);
743 OUString arrLabelString
;
744 CFStringRef arrLabel
;
747 /* Go through each element - since the elements here do not have
748 * unique keys like the ones in dictionaries, we create a unique
749 * key out of the id of the element in the array (the first
750 * element gets a 0 plopped onto the end of it, the second a 1...
751 * As with dictionaries, the elements could be anything, including
752 * another array, so we have to run this method recursively on
753 * each element, storing the resulting MacabHeader into an array,
754 * which we then combine into one MacabHeader that is returned.
756 for(i
= 0; i
< arrLength
; i
++)
758 arrValue
= CFArrayGetValueAtIndex(static_cast<CFArrayRef
>(_propertyValue
), i
);
759 arrType
= getABTypeFromCFType( CFGetTypeID(arrValue
) );
760 arrLabelString
= propertyNameString
+ OUString::number(i
);
761 arrLabel
= OUStringToCFString(arrLabelString
);
762 auto hdr
= std::unique_ptr
<MacabHeader
>(createHeaderForProperty(arrType
, arrValue
, arrLabel
));
764 hdr
= std::make_unique
<MacabHeader
>();
765 length
+= hdr
->getSize();
767 arrHeaders
.push_back(std::move(hdr
));
770 headerNames
= new macabfield
*[length
];
771 for(i
= 0, j
= 0, k
= 0; i
< length
; i
++,k
++)
773 while(arrHeaders
[j
]->getSize() == k
)
779 headerNames
[i
] = arrHeaders
[j
]->copy(k
);
789 /* If we succeeded at adding elements to the headerNames array, then
790 * length will no longer be 0. If it is, create a new MacabHeader
791 * out of the headerNames (after weeding out duplicate headers), and
792 * then return the result. If the length is still 0, return NULL: we
793 * failed to create a MacabHeader out of this property.
797 manageDuplicateHeaders(headerNames
, length
);
798 MacabHeader
*headerResult
= new MacabHeader(length
, headerNames
);
799 for(sal_Int32 i
= 0; i
< length
; ++i
)
800 delete headerNames
[i
];
801 delete [] headerNames
;
809 /* Create a MacabRecord out of an ABRecord, using a given MacabHeader and
810 * the record's type. We go through each property for this record type
811 * then process it much like we processed the header (above), with two
812 * exceptions: if we come upon something not in the header, we ignore it
813 * (it's something we don't want to add), and once we find a corresponding
814 * location in the header, we store the property and the property type in
815 * a macabfield. (For the header, we stored the property type and the name
816 * of the property as a CFString.)
818 MacabRecord
*MacabRecords::createMacabRecord(const ABRecordRef _abrecord
, const MacabHeader
*_header
, const CFStringRef _recordType
) const
820 /* The new record that we will create... */
821 MacabRecord
*macabRecord
= new MacabRecord(_header
->getSize());
823 CFArrayRef recordProperties
= ABCopyArrayOfPropertiesForRecordType(addressBook
, _recordType
);
824 sal_Int32 numProperties
= static_cast<sal_Int32
>(CFArrayGetCount(recordProperties
));
828 CFTypeRef propertyValue
;
829 ABPropertyType propertyType
;
831 CFStringRef propertyName
, localizedPropertyName
;
832 OUString propertyNameString
;
833 for(i
= 0; i
< numProperties
; i
++)
835 propertyName
= static_cast<CFStringRef
>(CFArrayGetValueAtIndex(recordProperties
, i
));
836 localizedPropertyName
= ABCopyLocalizedPropertyOrLabel(propertyName
);
837 propertyNameString
= CFStringToOUString(localizedPropertyName
);
838 CFRelease(localizedPropertyName
);
840 /* Get the property's value */
841 propertyValue
= ABRecordCopyValue(_abrecord
,propertyName
);
842 if(propertyValue
!= nullptr)
844 propertyType
= ABTypeOfProperty(addressBook
, _recordType
, propertyName
);
845 if(propertyType
!= kABErrorInProperty
)
846 insertPropertyIntoMacabRecord(propertyType
, macabRecord
, _header
, propertyNameString
, propertyValue
);
848 CFRelease(propertyValue
);
851 CFRelease(recordProperties
);
856 /* Inserts a given property into a MacabRecord. This method calls another
857 * method by the same name after getting the property type (it only
858 * receives the property value). It is called when we aren't given the
859 * property's type already.
861 void MacabRecords::insertPropertyIntoMacabRecord(MacabRecord
*_abrecord
, const MacabHeader
*_header
, const OUString
& _propertyName
, const CFTypeRef _propertyValue
) const
863 CFTypeID cf_type
= CFGetTypeID(_propertyValue
);
864 ABPropertyType ab_type
= getABTypeFromCFType( cf_type
);
866 if(ab_type
!= kABErrorInProperty
)
867 insertPropertyIntoMacabRecord(ab_type
, _abrecord
, _header
, _propertyName
, _propertyValue
);
871 /* Inserts a given property into a MacabRecord. This method is recursive
872 * because properties can contain many sub-properties.
874 void MacabRecords::insertPropertyIntoMacabRecord(const ABPropertyType _propertyType
, MacabRecord
*_abrecord
, const MacabHeader
*_header
, const OUString
& _propertyName
, const CFTypeRef _propertyValue
) const
876 /* If there is no value, return */
877 if(_propertyValue
== nullptr)
880 /* The main switch statement */
881 switch(_propertyType
)
884 case kABStringProperty
:
885 case kABRealProperty
:
886 case kABIntegerProperty
:
887 case kABDateProperty
:
889 /* Only scalars actually insert a property into the MacabRecord.
890 * In all other cases, this method is called recursively until a
891 * scalar type, an error, or an unknown type are found.
892 * Because of that, the following checks only occur for this type.
893 * We store whether we have successfully placed this property
894 * into the MacabRecord (or whether an unrecoverable error occurred).
895 * Then, we try over and over again to place the property into the
896 * record. There are three possible results:
898 * 2) There is already a property stored at the column of this name,
899 * in which case we have a duplicate header (see the method
900 * manageDuplicateHeaders()). If that is the case, we add an ID
901 * to the end of the column name in the same format as we do in
902 * manageDuplicateHeaders() and try again.
903 * 3) No column of this name exists in the header. In this case,
904 * there is nothing we can do: we have failed to place this
905 * property into the record.
907 bool bPlaced
= false;
908 OUString columnName
= _propertyName
;
911 // A big safeguard to prevent two fields from having the same name.
914 sal_Int32 columnNumber
= _header
->getColumnNumber(columnName
);
916 if(columnNumber
!= -1)
918 // collision! A property already exists here!
919 if(_abrecord
->get(columnNumber
) != nullptr)
923 columnName
= _propertyName
+ " (" + OUString::number(i
) + ")";
929 _abrecord
->insertAtColumn(_propertyValue
, _propertyType
, columnNumber
);
937 case kABArrayProperty
:
939 /* An array is basically just a list of anything, so all we do
940 * is go through the array, and rerun this method recursively
943 sal_Int32 arrLength
= static_cast<sal_Int32
>(CFArrayGetCount(static_cast<CFArrayRef
>(_propertyValue
)));
945 OUString newPropertyName
;
947 /* Going through each element... */
948 for(i
= 0; i
< arrLength
; i
++)
950 const void *arrValue
= CFArrayGetValueAtIndex(static_cast<CFArrayRef
>(_propertyValue
), i
);
951 newPropertyName
= _propertyName
+ OUString::number(i
);
952 insertPropertyIntoMacabRecord(_abrecord
, _header
, newPropertyName
, arrValue
);
960 case kABDictionaryProperty
:
962 /* A dictionary is basically a hashmap. Technically, it can
963 * hold any object as a key and any object as a value.
964 * For our case, we assume that the key is a string (so that
965 * we can use the key to get the column name and match it against
966 * the header), but we don't assume anything about the value, so
967 * we run this method recursively (or, rather, we run the version
968 * of this method for when we don't know the object's type) until
969 * we hit a scalar value.
972 sal_Int32 numRecords
= static_cast<sal_Int32
>(CFDictionaryGetCount(static_cast<CFDictionaryRef
>(_propertyValue
)));
973 OUString dictKeyString
;
975 OUString newPropertyName
;
977 /* Unfortunately, the only way to get both keys and values out
978 * of a dictionary in Carbon is to get them all at once, so we
981 CFStringRef
*dictKeys
;
982 CFStringRef localizedDictKey
;
983 CFTypeRef
*dictValues
;
984 dictKeys
= static_cast<CFStringRef
*>(malloc(sizeof(CFStringRef
)*numRecords
));
985 dictValues
= static_cast<CFTypeRef
*>(malloc(sizeof(CFTypeRef
)*numRecords
));
986 CFDictionaryGetKeysAndValues(static_cast<CFDictionaryRef
>(_propertyValue
), reinterpret_cast<const void **>(dictKeys
), dictValues
);
988 /* Going through each element... */
989 for(i
= 0; i
< numRecords
; i
++)
991 localizedDictKey
= ABCopyLocalizedPropertyOrLabel(dictKeys
[i
]);
992 dictKeyString
= CFStringToOUString(localizedDictKey
);
993 CFRelease(localizedDictKey
);
994 newPropertyName
= _propertyName
+ ": " + fixLabel(dictKeyString
);
995 insertPropertyIntoMacabRecord(_abrecord
, _header
, newPropertyName
, dictValues
[i
]);
1004 case kABMultiIntegerProperty
:
1005 case kABMultiDateProperty
:
1006 case kABMultiStringProperty
:
1007 case kABMultiRealProperty
:
1008 case kABMultiDataProperty
:
1009 case kABMultiDictionaryProperty
:
1010 case kABMultiArrayProperty
:
1012 /* All scalar multivalues are handled in the same way. Each element
1013 * is a label and a value. All labels are strings
1014 * (kABStringProperty), and all values have the same type
1015 * (which is the type of the multivalue minus 255, or as
1016 * Carbon's list of property types has it, minus 0x100.
1017 * We just get the correct type, then go through each element
1018 * and get the label and value and print them in a list.
1022 sal_Int32 multiLength
= ABMultiValueCount(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)));
1023 CFStringRef multiLabel
, localizedMultiLabel
;
1024 CFTypeRef multiValue
;
1025 OUString multiLabelString
, newPropertyName
;
1026 ABPropertyType multiType
= static_cast<ABPropertyType
>(ABMultiValuePropertyType(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
))) - 0x100);
1028 /* Go through each element... */
1029 for(i
= 0; i
< multiLength
; i
++)
1031 /* Label and value */
1032 multiLabel
= ABMultiValueCopyLabelAtIndex(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)), i
);
1033 multiValue
= ABMultiValueCopyValueAtIndex(static_cast<ABMutableMultiValueRef
>(const_cast<void *>(_propertyValue
)), i
);
1035 localizedMultiLabel
= ABCopyLocalizedPropertyOrLabel(multiLabel
);
1036 multiLabelString
= CFStringToOUString(localizedMultiLabel
);
1037 newPropertyName
= _propertyName
+ ": " + fixLabel(multiLabelString
);
1038 insertPropertyIntoMacabRecord(multiType
, _abrecord
, _header
, newPropertyName
, multiValue
);
1040 /* free our variables */
1041 CFRelease(multiLabel
);
1042 CFRelease(localizedMultiLabel
);
1043 CFRelease(multiValue
);
1048 /* Unhandled types */
1049 case kABErrorInProperty
:
1050 case kABDataProperty
:
1052 /* An error, as far as I have seen, only shows up as a type
1053 * returned by a function for dictionaries when the dictionary
1054 * holds many types of values. Since we do not use that function,
1055 * it shouldn't come up. I have yet to see the kABDataProperty,
1056 * and I am not sure how to represent it as a string anyway,
1057 * since it appears to just be a bunch of bytes. Assumably, if
1058 * these bytes made up a string, the type would be
1059 * kABStringProperty. I think that this is used when we are not
1060 * sure what the type is (e.g., it could be a string or a number).
1061 * That being the case, I still don't know how to represent it.
1062 * And, default should never come up, since we've exhausted all
1063 * of the possible types for ABPropertyType, but... just in case.
1071 ABPropertyType
MacabRecords::getABTypeFromCFType(const CFTypeID cf_type
) const
1073 for(auto const & i
: lcl_CFTypes
)
1078 return static_cast<ABPropertyType
>(i
.ab
);
1081 return kABErrorInProperty
;
1085 sal_Int32
MacabRecords::size() const
1087 return currentRecord
;
1091 MacabRecords
*MacabRecords::begin()
1097 MacabRecords::iterator::iterator ()
1102 MacabRecords::iterator
& MacabRecords::iterator::operator= (MacabRecords
*_records
)
1110 void MacabRecords::iterator::operator++ ()
1116 bool MacabRecords::iterator::operator!= (const sal_Int32 i
) const
1122 bool MacabRecords::iterator::operator== (const sal_Int32 i
) const
1128 MacabRecord
*MacabRecords::iterator::operator* () const
1130 return records
->getRecord(id
);
1134 sal_Int32
MacabRecords::end() const
1136 return currentRecord
;
1140 void MacabRecords::swap(const sal_Int32 _id1
, const sal_Int32 _id2
)
1142 MacabRecord
*swapRecord
= records
[_id1
];
1144 records
[_id1
] = records
[_id2
];
1145 records
[_id2
] = swapRecord
;
1149 void MacabRecords::setName(const OUString
& _sName
)
1155 OUString
const & MacabRecords::getName() const
1160 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */