LibreOffice Module connectivity (master) 1
MacabRecords.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21
22#include <memory>
23#include <utility>
24#include <vector>
25
26#include "MacabRecords.hxx"
27#include "MacabRecord.hxx"
28#include "MacabHeader.hxx"
29#include "macabutilities.hxx"
30
31#include <premac.h>
32#include <Carbon/Carbon.h>
33#include <AddressBook/ABAddressBookC.h>
34#include <postmac.h>
35#include <com/sun/star/util/DateTime.hpp>
36
37using namespace connectivity::macab;
38using namespace com::sun::star::util;
39
40namespace {
41
42void manageDuplicateHeaders(macabfield **_headerNames, const sal_Int32 _length)
43{
44 /* If we have two cases of, say, phone: home, this makes it:
45 * phone: home (1)
46 * phone: home (2)
47 */
48 sal_Int32 i, j;
49 sal_Int32 count;
50 for(i = _length-1; i >= 0; i--)
51 {
52 count = 1;
53 for( j = i-1; j >= 0; j--)
54 {
55 if(CFEqual(_headerNames[i]->value, _headerNames[j]->value))
56 {
57 count++;
58 }
59 }
60
61 // duplicate!
62 if(count != 1)
63 {
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);
69 }
70 }
71}
72
73}
74
75MacabRecords::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)
78{
79 /* Variables constructed... */
82}
83
84
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
91 * header.
92 */
94 : recordsSize(_copy->recordsSize), currentRecord(0), recordType(kABPersonRecordType),
95 header(nullptr), records(new MacabRecord *[recordsSize]), addressBook(_copy->addressBook),
96 m_sName(_copy->m_sName)
97{
98 /* Variables constructed... */
101}
102
103
104MacabRecords::MacabRecords(const ABAddressBookRef _addressBook)
105 : recordsSize(0), currentRecord(0), recordType(kABPersonRecordType),
106 header(nullptr), records(nullptr), addressBook(_addressBook)
107{
108 /* Variables constructed... */
111}
112
113
115{
116
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
120 * NULL.
121 */
122 if(records != nullptr)
123 {
124 sal_Int32 i;
125
126 for(i = 0; i < recordsSize; i++)
127 delete records[i];
128
129 delete [] records;
130 }
131
132 if(header != nullptr)
133 delete header;
134
135 /* We can handle both default record Address Book record types in
136 * this method, though only kABPersonRecordType is ever used.
137 */
138 CFArrayRef allRecords;
139 if(CFStringCompare(recordType, kABPersonRecordType, 0) == kCFCompareEqualTo)
140 allRecords = ABCopyArrayOfAllPeople(addressBook);
141 else
142 allRecords = ABCopyArrayOfAllGroups(addressBook);
143
144 ABRecordRef record;
145 sal_Int32 i;
146 recordsSize = static_cast<sal_Int32>(CFArrayGetCount(allRecords));
148
149 /* First, we create the header... */
151
152 /* Then, we create each of the records... */
153 for(i = 0; i < recordsSize; i++)
154 {
155 record = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(allRecords, i));
157 }
159
160 CFRelease(allRecords);
161}
162
163
165{
166}
167
168
170{
171 if(header != nullptr)
172 delete header;
173 header = _header;
174}
175
176
178{
179 return header;
180}
181
182
183/* Inserts a MacabRecord at a given location. If there is already a
184 * MacabRecord at that location, return it.
185 */
186MacabRecord *MacabRecords::insertRecord(MacabRecord *_newRecord, const sal_Int32 _location)
187{
188 MacabRecord *oldRecord;
189
190 /* If the location is greater than the current allocated size of this
191 * MacabRecords, allocate more space.
192 */
193 if(_location >= recordsSize)
194 {
195 sal_Int32 i;
196 MacabRecord **newRecordsArray = new MacabRecord *[_location+1];
197 for(i = 0; i < recordsSize; i++)
198 {
199 newRecordsArray[i] = records[i];
200 }
201 delete [] records;
202 records = newRecordsArray;
203 }
204
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).
208 */
209 if(_location >= currentRecord)
210 currentRecord = _location+1;
211
212 oldRecord = records[_location];
213 records[_location] = _newRecord;
214 return oldRecord;
215}
216
217
218/* Insert a record at the next available place. */
220{
221 insertRecord(_newRecord, currentRecord);
222}
223
224
225MacabRecord *MacabRecords::getRecord(const sal_Int32 _location) const
226{
227 if(_location >= recordsSize)
228 return nullptr;
229 return records[_location];
230}
231
232
233macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, const sal_Int32 _columnNumber) const
234{
235 if(_recordNumber >= recordsSize)
236 return nullptr;
237
238 MacabRecord *record = records[_recordNumber];
239
240 if(_columnNumber < 0 || _columnNumber >= record->getSize())
241 return nullptr;
242
243 return record->get(_columnNumber);
244}
245
246
247macabfield *MacabRecords::getField(const sal_Int32 _recordNumber, std::u16string_view _columnName)
248 const
249{
250 if(header != nullptr)
251 {
252 sal_Int32 columnNumber = header->getColumnNumber(_columnName);
253 if(columnNumber == -1)
254 return nullptr;
255
256 return getField(_recordNumber, columnNumber);
257 }
258 else
259 {
260 // error: shouldn't access field with null header!
261 return nullptr;
262 }
263}
264
265
266sal_Int32 MacabRecords::getFieldNumber(std::u16string_view _columnName) const
267{
268 if(header != nullptr)
269 return header->getColumnNumber(_columnName);
270 else
271 // error: shouldn't access field with null header!
272 return -1;
273}
274
275
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.
289 */
291{
292 lcl_CFTypes = {
293 {CFNumberGetTypeID(), kABIntegerProperty},
294 {CFStringGetTypeID(), kABStringProperty},
295 {CFDateGetTypeID(), kABDateProperty},
296 {CFArrayGetTypeID(), kABArrayProperty},
297 {CFDictionaryGetTypeID(), kABDictionaryProperty},
298 {CFDataGetTypeID(), kABDataProperty}};
299}
300
301
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.
305 */
307{
309 kABTitleProperty, kABFirstNameProperty, kABLastNameProperty, kABOrganizationProperty,
310 kABAddressProperty, kABPhoneProperty, kABEmailProperty};
311}
312
313
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).
319 */
320MacabHeader *MacabRecords::createHeaderForRecordType(const CFArrayRef _records, const CFStringRef _recordType) const
321{
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.
330 *
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).
334 *
335 * Note: required properties are constructed in the method
336 * bootstrap_requiredProperties() (above).
337 */
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();
343
344 /* While searching through the properties for required properties, these
345 * sal_Bools will keep track of what we have found.
346 */
347 bool bFoundRequiredProperties[requiredProperties.size()];
348
349
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.
356 */
357 MacabHeader *headerDataForProperty;
358 MacabHeader *lcl_header = new MacabHeader();
359 MacabHeader *nonRequiredHeader = new MacabHeader();
360
361 /* Other variables... */
362 sal_Int32 k;
363 ABRecordRef record;
364 CFStringRef property;
365
366
367 /* Allocate and initialize... */
368 nonRequiredProperties = new CFStringRef[numNonRequiredProperties];
369 k = 0;
370 for(std::vector<CFStringRef>::size_type i = 0; i < requiredProperties.size(); i++)
371 bFoundRequiredProperties[i] = false;
372
373 /* Determine the non-required properties... */
374 for(sal_Int32 i = 0; i < numProperties; i++)
375 {
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++)
379 {
380 if(CFEqual(property, requiredProperties[j]))
381 {
382 bFoundProperty = true;
383 bFoundRequiredProperties[j] = true;
384 break;
385 }
386 }
387
388 if(!bFoundProperty)
389 {
390 /* If we have found too many non-required properties */
391 if(k == numNonRequiredProperties)
392 {
393 k++; // so that the OSL_ENSURE below fails
394 break;
395 }
396 nonRequiredProperties[k] = property;
397 k++;
398 }
399 }
400
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");
405
406 /* Fill the header with required properties first... */
407 for(std::vector<CFStringRef>::size_type i = 0; i < requiredProperties.size(); i++)
408 {
409 if(bFoundRequiredProperties[i])
410 {
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
420 * get all of them.
421 */
422 for(sal_Int32 j = 0; j < numRecords; j++)
423 {
424 record = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(_records, j));
425 headerDataForProperty = createHeaderForProperty(record,requiredProperties[i],_recordType,true);
426 if(headerDataForProperty != nullptr)
427 {
428 (*lcl_header) += headerDataForProperty;
429 delete headerDataForProperty;
430 }
431 }
432 }
433 else
434 {
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());
438 }
439 }
440
441 /* And now, non-required properties... */
442 for(sal_Int32 i = 0; i < numRecords; i++)
443 {
444 record = const_cast<ABRecordRef>(CFArrayGetValueAtIndex(_records, i));
445
446 for(sal_Int32 j = 0; j < numNonRequiredProperties; j++)
447 {
448 property = nonRequiredProperties[j];
449 headerDataForProperty = createHeaderForProperty(record,property,_recordType,false);
450 if(headerDataForProperty != nullptr)
451 {
452 (*nonRequiredHeader) += headerDataForProperty;
453 delete headerDataForProperty;
454 }
455 }
456
457 }
458 nonRequiredHeader->sortRecord();
459
460 (*lcl_header) += nonRequiredHeader;
461 delete nonRequiredHeader;
462
463 CFRelease(allProperties);
464 delete [] nonRequiredProperties;
465
466 return lcl_header;
467}
468
469
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.
473 */
474MacabHeader *MacabRecords::createHeaderForProperty(const ABRecordRef _record, const CFStringRef _propertyName, const CFStringRef _recordType, const bool _isPropertyRequired) const
475{
476 // local variables
477 CFStringRef localizedPropertyName;
478 CFTypeRef propertyValue;
479 ABPropertyType propertyType;
481
482 /* Get the property's value */
483 propertyValue = ABRecordCopyValue(_record,_propertyName);
484 if(propertyValue == nullptr && !_isPropertyRequired)
485 return nullptr;
486
487 propertyType = ABTypeOfProperty(addressBook, _recordType, _propertyName);
488 localizedPropertyName = ABCopyLocalizedPropertyOrLabel(_propertyName);
489
490 result = createHeaderForProperty(propertyType, propertyValue, localizedPropertyName);
491
492 if(propertyValue != nullptr)
493 CFRelease(propertyValue);
494
495 return result;
496}
497
498
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.
502 */
503MacabHeader *MacabRecords::createHeaderForProperty(const ABPropertyType _propertyType, const CFTypeRef _propertyValue, const CFStringRef _propertyName) const
504{
505 macabfield **headerNames = nullptr;
506 sal_Int32 length = 0;
507
508 switch(_propertyType)
509 {
510 /* Scalars */
511 case kABStringProperty:
512 case kABRealProperty:
513 case kABIntegerProperty:
514 case kABDateProperty:
515 length = 1;
516 headerNames = new macabfield *[1];
517 headerNames[0] = new macabfield;
518 headerNames[0]->value = _propertyName;
519 headerNames[0]->type = _propertyType;
520 break;
521
522 /* Multi-scalars */
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
529 * actually exists.
530 */
531 if(_propertyValue != nullptr)
532 {
533 sal_Int32 i;
534
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);
541
542 length = multiLength;
543 headerNames = new macabfield *[multiLength];
544 multiPropertyString = CFStringToOUString(_propertyName);
545
546 /* Go through each element, and - since each element is a scalar -
547 * just create a new macabfield for it.
548 */
549 for(i = 0; i < multiLength; i++)
550 {
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;
560 }
561 }
562 break;
563
564 /* Multi-array or dictionary */
565 case kABMultiArrayProperty:
566 case kABMultiDictionaryProperty:
567 /* For non-scalars, we can only get more information if the property
568 * actually exists.
569 */
570 if(_propertyValue != nullptr)
571 {
572 sal_Int32 i,j,k;
573
574 // Total number of multi-array or multi-dictionary elements.
575 sal_Int32 multiLengthFirstLevel = ABMultiValueCount(static_cast<ABMutableMultiValueRef>(const_cast<void *>(_propertyValue)));
576
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.
581 */
582 sal_Int32 multiLengthSecondLevel = 0;
583
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);
590
591 multiPropertyString = CFStringToOUString(_propertyName);
592
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
597 * into a single one.
598 */
599 for(i = 0; i < multiLengthFirstLevel; i++)
600 {
601 /* label */
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)
606 {
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));
613 if (!hdr)
614 hdr = std::make_unique<MacabHeader>();
615 multiLengthSecondLevel += hdr->getSize();
616 }
617 else
618 {
619 hdr = std::make_unique<MacabHeader>();
620 }
621 if(multiValue)
622 CFRelease(multiValue);
623 if(multiLabel)
624 CFRelease(multiLabel);
625 multiHeaders.push_back(std::move(hdr));
626 }
627
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).
632 */
633 length = multiLengthSecondLevel;
634 headerNames = new macabfield *[multiLengthSecondLevel];
635
636 for(i = 0, j = 0, k = 0; i < multiLengthSecondLevel; i++,k++)
637 {
638 while(multiHeaders[j]->getSize() == k)
639 {
640 j++;
641 k = 0;
642 }
643
644 headerNames[i] = multiHeaders[j]->copy(k);
645 }
646 }
647 break;
648
649 /* Dictionary */
650 case kABDictionaryProperty:
651 /* For non-scalars, we can only get more information if the property
652 * actually exists.
653 */
654 if(_propertyValue != nullptr)
655 {
656 /* Assume all keys are strings */
657 sal_Int32 numRecords = static_cast<sal_Int32>(CFDictionaryGetCount(static_cast<CFDictionaryRef>(_propertyValue)));
658
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.
662 */
663 CFStringRef *dictKeys;
664 CFTypeRef *dictValues;
665
666 sal_Int32 i,j,k;
667 OUString dictKeyString, propertyNameString;
668 ABPropertyType dictType;
669 MacabHeader **dictHeaders = new MacabHeader *[numRecords];
670 OUString dictLabelString;
671 CFStringRef dictLabel, localizedDictKey;
672
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);
677
678 propertyNameString = CFStringToOUString(_propertyName);
679
680 length = 0;
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
688 * one.
689 */
690 for(i = 0; i < numRecords; i++)
691 {
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);
698 if (!dictHeaders[i])
699 dictHeaders[i] = new MacabHeader();
700 length += dictHeaders[i]->getSize();
701 CFRelease(dictLabel);
702 CFRelease(localizedDictKey);
703 }
704
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.
708 */
709 headerNames = new macabfield *[length];
710 for(i = 0, j = 0, k = 0; i < length; i++,k++)
711 {
712 while(dictHeaders[j]->getSize() == k)
713 {
714 j++;
715 k = 0;
716 }
717
718 headerNames[i] = dictHeaders[j]->copy(k);
719 }
720
721 for(i = 0; i < numRecords; i++)
722 delete dictHeaders[i];
723
724 delete [] dictHeaders;
725 free(dictKeys);
726 free(dictValues);
727 }
728 break;
729
730 /* Array */
731 case kABArrayProperty:
732 /* For non-scalars, we can only get more information if the property
733 * actually exists.
734 */
735 if(_propertyValue != nullptr)
736 {
737 sal_Int32 arrLength = static_cast<sal_Int32>(CFArrayGetCount(static_cast<CFArrayRef>(_propertyValue)));
738 sal_Int32 i,j,k;
739 CFTypeRef arrValue;
740 ABPropertyType arrType;
741 std::vector<std::unique_ptr<MacabHeader>> arrHeaders;
742 OUString propertyNameString = CFStringToOUString(_propertyName);
743 OUString arrLabelString;
744 CFStringRef arrLabel;
745
746 length = 0;
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.
755 */
756 for(i = 0; i < arrLength; i++)
757 {
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));
763 if (!hdr)
764 hdr = std::make_unique<MacabHeader>();
765 length += hdr->getSize();
766 CFRelease(arrLabel);
767 arrHeaders.push_back(std::move(hdr));
768 }
769
770 headerNames = new macabfield *[length];
771 for(i = 0, j = 0, k = 0; i < length; i++,k++)
772 {
773 while(arrHeaders[j]->getSize() == k)
774 {
775 j++;
776 k = 0;
777 }
778
779 headerNames[i] = arrHeaders[j]->copy(k);
780 }
781 }
782 break;
783
784 default:
785 break;
786
787 }
788
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.
794 */
795 if(length != 0)
796 {
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;
802 return headerResult;
803 }
804 else
805 return nullptr;
806}
807
808
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.)
817 */
818MacabRecord *MacabRecords::createMacabRecord(const ABRecordRef _abrecord, const MacabHeader *_header, const CFStringRef _recordType) const
819{
820 /* The new record that we will create... */
821 MacabRecord *macabRecord = new MacabRecord(_header->getSize());
822
823 CFArrayRef recordProperties = ABCopyArrayOfPropertiesForRecordType(addressBook, _recordType);
824 sal_Int32 numProperties = static_cast<sal_Int32>(CFArrayGetCount(recordProperties));
825
826 sal_Int32 i;
827
828 CFTypeRef propertyValue;
829 ABPropertyType propertyType;
830
831 CFStringRef propertyName, localizedPropertyName;
832 OUString propertyNameString;
833 for(i = 0; i < numProperties; i++)
834 {
835 propertyName = static_cast<CFStringRef>(CFArrayGetValueAtIndex(recordProperties, i));
836 localizedPropertyName = ABCopyLocalizedPropertyOrLabel(propertyName);
837 propertyNameString = CFStringToOUString(localizedPropertyName);
838 CFRelease(localizedPropertyName);
839
840 /* Get the property's value */
841 propertyValue = ABRecordCopyValue(_abrecord,propertyName);
842 if(propertyValue != nullptr)
843 {
844 propertyType = ABTypeOfProperty(addressBook, _recordType, propertyName);
845 if(propertyType != kABErrorInProperty)
846 insertPropertyIntoMacabRecord(propertyType, macabRecord, _header, propertyNameString, propertyValue);
847
848 CFRelease(propertyValue);
849 }
850 }
851 CFRelease(recordProperties);
852 return macabRecord;
853}
854
855
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.
860 */
861void MacabRecords::insertPropertyIntoMacabRecord(MacabRecord *_abrecord, const MacabHeader *_header, const OUString& _propertyName, const CFTypeRef _propertyValue) const
862{
863 CFTypeID cf_type = CFGetTypeID(_propertyValue);
864 ABPropertyType ab_type = getABTypeFromCFType( cf_type );
865
866 if(ab_type != kABErrorInProperty)
867 insertPropertyIntoMacabRecord(ab_type, _abrecord, _header, _propertyName, _propertyValue);
868}
869
870
871/* Inserts a given property into a MacabRecord. This method is recursive
872 * because properties can contain many sub-properties.
873 */
874void MacabRecords::insertPropertyIntoMacabRecord(const ABPropertyType _propertyType, MacabRecord *_abrecord, const MacabHeader *_header, const OUString& _propertyName, const CFTypeRef _propertyValue) const
875{
876 /* If there is no value, return */
877 if(_propertyValue == nullptr)
878 return;
879
880 /* The main switch statement */
881 switch(_propertyType)
882 {
883 /* Scalars */
884 case kABStringProperty:
885 case kABRealProperty:
886 case kABIntegerProperty:
887 case kABDateProperty:
888 {
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:
897 * 1) Success!
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.
906 */
907 bool bPlaced = false;
908 OUString columnName = _propertyName;
909 sal_Int32 i = 1;
910
911 // A big safeguard to prevent two fields from having the same name.
912 while(!bPlaced)
913 {
914 sal_Int32 columnNumber = _header->getColumnNumber(columnName);
915 bPlaced = true;
916 if(columnNumber != -1)
917 {
918 // collision! A property already exists here!
919 if(_abrecord->get(columnNumber) != nullptr)
920 {
921 bPlaced = false;
922 i++;
923 columnName = _propertyName + " (" + OUString::number(i) + ")";
924 }
925
926 // success!
927 else
928 {
929 _abrecord->insertAtColumn(_propertyValue, _propertyType, columnNumber);
930 }
931 }
932 }
933 }
934 break;
935
936 /* Array */
937 case kABArrayProperty:
938 {
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
941 * on each element.
942 */
943 sal_Int32 arrLength = static_cast<sal_Int32>(CFArrayGetCount(static_cast<CFArrayRef>(_propertyValue)));
944 sal_Int32 i;
945 OUString newPropertyName;
946
947 /* Going through each element... */
948 for(i = 0; i < arrLength; i++)
949 {
950 const void *arrValue = CFArrayGetValueAtIndex(static_cast<CFArrayRef>(_propertyValue), i);
951 newPropertyName = _propertyName + OUString::number(i);
952 insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, arrValue);
953 CFRelease(arrValue);
954 }
955
956 }
957 break;
958
959 /* Dictionary */
960 case kABDictionaryProperty:
961 {
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.
970 */
971
972 sal_Int32 numRecords = static_cast<sal_Int32>(CFDictionaryGetCount(static_cast<CFDictionaryRef>(_propertyValue)));
973 OUString dictKeyString;
974 sal_Int32 i;
975 OUString newPropertyName;
976
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
979 * do that.
980 */
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);
987
988 /* Going through each element... */
989 for(i = 0; i < numRecords; i++)
990 {
991 localizedDictKey = ABCopyLocalizedPropertyOrLabel(dictKeys[i]);
992 dictKeyString = CFStringToOUString(localizedDictKey);
993 CFRelease(localizedDictKey);
994 newPropertyName = _propertyName + ": " + fixLabel(dictKeyString);
995 insertPropertyIntoMacabRecord(_abrecord, _header, newPropertyName, dictValues[i]);
996 }
997
998 free(dictKeys);
999 free(dictValues);
1000 }
1001 break;
1002
1003 /* Multivalue */
1004 case kABMultiIntegerProperty:
1005 case kABMultiDateProperty:
1006 case kABMultiStringProperty:
1007 case kABMultiRealProperty:
1008 case kABMultiDataProperty:
1009 case kABMultiDictionaryProperty:
1010 case kABMultiArrayProperty:
1011 {
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.
1019 */
1020
1021 sal_Int32 i;
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);
1027
1028 /* Go through each element... */
1029 for(i = 0; i < multiLength; i++)
1030 {
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);
1034
1035 localizedMultiLabel = ABCopyLocalizedPropertyOrLabel(multiLabel);
1036 multiLabelString = CFStringToOUString(localizedMultiLabel);
1037 newPropertyName = _propertyName + ": " + fixLabel(multiLabelString);
1038 insertPropertyIntoMacabRecord(multiType, _abrecord, _header, newPropertyName, multiValue);
1039
1040 /* free our variables */
1041 CFRelease(multiLabel);
1042 CFRelease(localizedMultiLabel);
1043 CFRelease(multiValue);
1044 }
1045 }
1046 break;
1047
1048 /* Unhandled types */
1049 case kABErrorInProperty:
1050 case kABDataProperty:
1051 default:
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.
1064 */
1065 break;
1066 }
1067
1068}
1069
1070
1071ABPropertyType MacabRecords::getABTypeFromCFType(const CFTypeID cf_type ) const
1072{
1073 for(auto const & i: lcl_CFTypes)
1074 {
1075 /* A match! */
1076 if(i.cf == cf_type)
1077 {
1078 return static_cast<ABPropertyType>(i.ab);
1079 }
1080 }
1081 return kABErrorInProperty;
1082}
1083
1084
1085sal_Int32 MacabRecords::size() const
1086{
1087 return currentRecord;
1088}
1089
1090
1092{
1093 return this;
1094}
1095
1096
1098{
1099}
1100
1101
1103{
1104 id = 0;
1105 records = _records;
1106 return *this;
1107}
1108
1109
1111{
1112 id++;
1113}
1114
1115
1116bool MacabRecords::iterator::operator!= (const sal_Int32 i) const
1117{
1118 return(id != i);
1119}
1120
1121
1122bool MacabRecords::iterator::operator== (const sal_Int32 i) const
1123{
1124 return(id == i);
1125}
1126
1127
1129{
1130 return records->getRecord(id);
1131}
1132
1133
1134sal_Int32 MacabRecords::end() const
1135{
1136 return currentRecord;
1137}
1138
1139
1140void MacabRecords::swap(const sal_Int32 _id1, const sal_Int32 _id2)
1141{
1142 MacabRecord *swapRecord = records[_id1];
1143
1144 records[_id1] = records[_id2];
1145 records[_id2] = swapRecord;
1146}
1147
1148
1149void MacabRecords::setName(const OUString& _sName)
1150{
1151 m_sName = _sName;
1152}
1153
1154
1155OUString const & MacabRecords::getName() const
1156{
1157 return m_sName;
1158}
1159
1160/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
constexpr sal_Int8 header[]
sal_Int32 getColumnNumber(std::u16string_view s) const
macabfield ** sortRecord(sal_Int32 _start, sal_Int32 _length)
void insertAtColumn(CFTypeRef _value, ABPropertyType _type, const sal_Int32 _column)
Definition: MacabRecord.cxx:66
macabfield * get(const sal_Int32 i) const
macabfield * copy(const sal_Int32 i) const
bool operator!=(const sal_Int32 i) const
bool operator==(const sal_Int32 i) const
iterator & operator=(MacabRecords *_records)
OUString const & getName() const
MacabRecords(const ABAddressBookRef _addressBook, MacabHeader *_header, MacabRecord **_records, sal_Int32 _numRecords)
MacabRecord * createMacabRecord(const ABRecordRef _abrecord, const MacabHeader *_header, const CFStringRef _recordType) const
void swap(const sal_Int32 _id1, const sal_Int32 _id2)
void setName(const OUString &_sName)
std::vector< lcl_CFType > lcl_CFTypes
void insertPropertyIntoMacabRecord(MacabRecord *_abrecord, const MacabHeader *_header, const OUString &_propertyName, const CFTypeRef _propertyValue) const
void setHeader(MacabHeader *_header)
sal_Int32 getFieldNumber(std::u16string_view _columnName) const
macabfield * getField(const sal_Int32 _recordNumber, const sal_Int32 _columnNumber) const
MacabHeader * getHeader() const
MacabHeader * createHeaderForRecordType(const CFArrayRef _records, const CFStringRef _recordType) const
MacabRecord * insertRecord(MacabRecord *_newRecord, const sal_Int32 _location)
MacabHeader * createHeaderForProperty(const ABRecordRef _record, const CFStringRef _propertyName, const CFStringRef _recordType, const bool _isPropertyRequired) const
ABPropertyType getABTypeFromCFType(const CFTypeID cf_type) const
MacabRecord * getRecord(const sal_Int32 _location) const
std::vector< CFStringRef > requiredProperties
OUString m_sName
OUString fixLabel(const OUString &_originalLabel)
CFStringRef OUStringToCFString(const OUString &aString)
OUString CFStringToOUString(const CFStringRef sOrig)
OUString newName(std::u16string_view aNewPrefix, std::u16string_view aOldPrefix, std::u16string_view old_Name)
int i
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
Definition: pq_tools.cxx:100
const char * columnName
Definition: pq_statics.cxx:56
sal_Int32 value
Definition: pq_statics.cxx:68
Any result