-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathContacts.cpp
213 lines (180 loc) · 6.63 KB
/
Contacts.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Contacts.cpp
// AmiKo-wx
//
// Created by Alex Bettarini on 1 Sep 2020
// Copyright © 2020 Ywesee GmbH. All rights reserved.
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <istream>
#include <wx/wx.h>
#include <wx/textfile.h>
#include <wx/filedlg.h>
#include "Contacts.hpp"
#include "Patient.hpp"
enum {
CONTACT_IMPORT_CSV_GOOGLE,
CONTACT_IMPORT_CSV_OUTLOOK,
CONTACT_IMPORT_VCARD
};
// /////////////////////////////////////////////////////////////////////////////
// Not in amiko-osx
// Adapted from http://www.zedwood.com/article/cpp-csv-parser
std::vector<std::string>
Contacts::csv_read_row(std::istream &in, char delimiter)
{
std::stringstream ss;
bool inquotes = false;
std::vector<std::string> row; //relying on RVO
while (in.good())
{
char c = in.get();
if (!inquotes && c=='"') // begin quotechar
{
inquotes = true;
}
else if (inquotes && c=='"') // quotechar
{
if ( in.peek() == '"') // 2 consecutive quotes resolve to 1
{
ss << (char)in.get();
}
else // end quotechar
{
inquotes = false;
}
}
else if (!inquotes && c==delimiter) // end of field
{
row.push_back( ss.str() );
ss.str("");
}
else if (!inquotes && (c=='\r' || c=='\n') )
{
if (in.peek()=='\n')
in.get();
row.push_back( ss.str() );
return row;
}
else
{
ss << c;
}
}
}
// 29
static std::vector<Patient *> groupOfContacts;
Contacts::Contacts()
{
}
// 40
std::vector<Patient *> Contacts::getAllContacts()
{
addAllContactsToArray(groupOfContacts);
return groupOfContacts;
}
// 49
// Read CSV file Google/Outlook
// See issue #30
void Contacts::addAllContactsToArray(std::vector<Patient *> &arrayOfContacts)
{
wxString wildCard = wxString::Format("CSV %s (*.csv;*.CSV)|*.csv;*.CSV", _("files"));
wxFileDialog fdlog(wxTheApp->GetTopWindow(),
_("Select Google/Outlook CSV file"),
wxEmptyString, // defaultDir
wxEmptyString, // defaultFile
wildCard,
wxFD_OPEN | wxFD_FILE_MUST_EXIST); // no wxFD_MULTIPLE
if (fdlog.ShowModal() != wxID_OK)
return;
std::ifstream in( fdlog.GetPath().c_str() );
if (in.fail()) {
std::cerr << "CSV file not found" << std::endl;
return;
}
std::vector<std::string> header;
if (in.good())
header = csv_read_row(in, ',');
int nCol = header.size();
// Create map to associate header cells with indexes
std::map<wxString, int> headerMap;
for (int i=0; i<nCol; i++)
headerMap[header[i]] = i;
// Pre-calculate the needed indexes for faster access later in the while loop
int idx_name = headerMap["Given Name"];
int idx_surname = headerMap["Family Name"];
int idx_birthdate = headerMap["Birthday"];
int idx_gender = headerMap["Gender"];
int idx_address = headerMap["Address 1 - Street"];
int idx_city = headerMap["Address 1 - City"];
int idx_zip = headerMap["Address 1 - Postal Code"];
int idx_country = headerMap["Address 1 - Country"];
// Extra checks for optional fields:
std::map<wxString, int>::iterator it;
it = headerMap.find("E-mail 1 - Value");
int idx_email = (it != headerMap.end()) ? it->second : -1;
it = headerMap.find("Phone 1 - Value");
int idx_phone = (it != headerMap.end()) ? it->second : -1;
// We cannog decide between Google or Outlook based on the number of columns,
// because the Google CSV format has a variable number of columns, depending on the data.
// The solution is to decide from the first header cell.
int csvType = (header[0] == "Name") ? CONTACT_IMPORT_CSV_GOOGLE : CONTACT_IMPORT_CSV_OUTLOOK;
switch (csvType) {
case CONTACT_IMPORT_CSV_GOOGLE:
arrayOfContacts.clear();
while (in.good())
{
std::vector<std::string> row = csv_read_row(in, ',');
if (row.size() != nCol) // sanity check
continue;
Patient *patient = new Patient;
patient->givenName = row[idx_name];
patient->familyName = row[idx_surname];
//patient->gender = row[idx_gender]; // TODO:
patient->birthDate = row[idx_birthdate]; // TODO:
patient->postalAddress = row[idx_address]; // See issue #39
patient->city = row[idx_city];
patient->zipCode = row[idx_zip];
patient->country = row[idx_country];
// Extra checks for optional fields:
if (idx_email >= 0) patient->emailAddress = row[idx_email];
if (idx_phone >= 0) patient->phoneNumber = row[idx_phone];
patient->databaseType = eAddressBook;
arrayOfContacts.push_back(patient);
}
break;
case CONTACT_IMPORT_CSV_OUTLOOK:
arrayOfContacts.clear();
while (in.good())
{
std::vector<std::string> row = csv_read_row(in, ',');
// Normally row.size() is nCol+1 because there is an extra trailing ','
// but we also allow nCol, just ban the case < nCol
if (row.size() < nCol) // sanity check
continue;
Patient *patient = new Patient;
patient->givenName = row[0]; // A
patient->familyName = row[2]; // C
//patient->gender = row[7]; // H TODO:
patient->birthDate = row[8]; // I
patient->emailAddress = row[14]; // O
patient->phoneNumber = row[17]; // R TODO: fallback S,U
patient->postalAddress = row[61]; // X TODO: fallback X,BJ
patient->city = row[65]; // BN
patient->zipCode = row[67]; // BP
patient->country = row[68]; // BQ
patient->databaseType = eAddressBook;
arrayOfContacts.push_back(patient);
}
break;
default:
std::cerr << "Not a valid Google/Outlook CSV file, " << nCol << " columns\n";
break;
}
std::clog << __FUNCTION__ << " Line " << __LINE__
<< ", # contacts:" << arrayOfContacts.size()
<< std::endl;
in.close();
}