1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
7 Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24/**
25 @file
26 This file is part of the API for handling calendar data and
27 defines the Person class.
28
29 @brief
30 Represents a person, by name and email address.
31
32 @author Cornelius Schumacher \<schumacher@kde.org\>
33 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
34*/
35
36#include "person.h"
37#include <QtCore/QRegExp>
38#include <QtCore/QDataStream>
39
40using namespace KCalCore;
41
42/**
43 Private class that helps to provide binary compatibility between releases.
44 @internal
45*/
46//@cond PRIVATE
47class KCalCore::Person::Private
48{
49public:
50 Private() : mCount(0) {}
51 QString mName; // person name
52 QString mEmail; // person email address
53 int mCount; // person reference count
54};
55//@endcond
56
57Person::Person() : d(new KCalCore::Person::Private)
58{
59}
60
61Person::Person(const QString &name, const QString &email)
62 : d(new KCalCore::Person::Private)
63{
64 d->mName = name;
65 d->mEmail = email;
66}
67
68Person::Person(const Person &person)
69 : d(new KCalCore::Person::Private(*person.d))
70{
71}
72
73Person::~Person()
74{
75 delete d;
76}
77
78bool KCalCore::Person::operator==(const Person &person) const
79{
80 return
81 d->mName == person.d->mName &&
82 d->mEmail == person.d->mEmail;
83}
84
85bool KCalCore::Person::operator!=(const Person &person) const
86{
87 return !(*this == person);
88}
89
90Person &KCalCore::Person::operator=(const Person &person)
91{
92 // check for self assignment
93 if (&person == this) {
94 return *this;
95 }
96
97 *d = *person.d;
98 return *this;
99}
100
101QString Person::fullName() const
102{
103 if (d->mName.isEmpty()) {
104 return d->mEmail;
105 } else {
106 if (d->mEmail.isEmpty()) {
107 return d->mName;
108 } else {
109 // Taken from KABC::Addressee::fullEmail
110 QString name = d->mName;
111 QRegExp needQuotes(QLatin1String("[^ 0-9A-Za-z\\x0080-\\xFFFF]"));
112 bool weNeedToQuote = name.indexOf(needQuotes) != -1;
113 if (weNeedToQuote) {
114 if (name[0] != QLatin1Char('"')) {
115 name.prepend(QLatin1Char('"'));
116 }
117 if (name[ name.length()-1 ] != QLatin1Char('"')) {
118 name.append(QLatin1Char('"'));
119 }
120 }
121 return name + QLatin1String(" <") + d->mEmail + QLatin1Char('>');
122 }
123 }
124}
125
126QString Person::name() const
127{
128 return d->mName;
129}
130
131QString Person::email() const
132{
133 return d->mEmail;
134}
135
136bool Person::isEmpty() const
137{
138 return d->mEmail.isEmpty() && d->mName.isEmpty();
139}
140
141void Person::setName(const QString &name)
142{
143 d->mName = name;
144}
145
146void Person::setEmail(const QString &email)
147{
148 if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
149 d->mEmail = email.mid(7);
150 } else {
151 d->mEmail = email;
152 }
153}
154
155bool Person::isValidEmail(const QString &email)
156{
157 int pos = email.lastIndexOf(QLatin1String("@"));
158 return (pos > 0) && (email.lastIndexOf(QLatin1String(".")) > pos) && ((email.length() - pos) > 4);
159}
160
161void Person::setCount(int count)
162{
163 d->mCount = count;
164}
165
166int Person::count() const
167{
168 return d->mCount;
169}
170
171uint qHash(const KCalCore::Person &key)
172{
173 return qHash(key.fullName());
174}
175
176QDataStream &KCalCore::operator<<(QDataStream &stream, const KCalCore::Person::Ptr &person)
177{
178 return stream << person->d->mName
179 << person->d->mEmail
180 << person->d->mCount;
181}
182
183QDataStream &KCalCore::operator>>(QDataStream &stream, Person::Ptr &person)
184{
185 QString name, email;
186 int count;
187
188 stream >> name >> email >> count;
189
190 Person::Ptr person_tmp(new Person(name, email));
191 person_tmp->setCount(count);
192 person.swap(person_tmp);
193 return stream;
194}
195
196// The following function was lifted directly from KPIMUtils
197// in order to eliminate the dependency on that library.
198// Any changes made here should be ported there, and vice versa.
199static bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
200{
201 name.clear();
202 mail.clear();
203
204 const int len = aStr.length();
205 const char cQuotes = '"';
206
207 bool bInComment = false;
208 bool bInQuotesOutsideOfEmail = false;
209 int i=0, iAd=0, iMailStart=0, iMailEnd=0;
210 QChar c;
211 unsigned int commentstack = 0;
212
213 // Find the '@' of the email address
214 // skipping all '@' inside "(...)" comments:
215 while (i < len) {
216 c = aStr[i];
217 if (QLatin1Char('(') == c) {
218 commentstack++;
219 }
220 if (QLatin1Char(')') == c) {
221 commentstack--;
222 }
223 bInComment = commentstack != 0;
224 if (QLatin1Char('"') == c && !bInComment) {
225 bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
226 }
227
228 if (!bInComment && !bInQuotesOutsideOfEmail) {
229 if (QLatin1Char('@') == c) {
230 iAd = i;
231 break; // found it
232 }
233 }
234 ++i;
235 }
236
237 if (!iAd) {
238 // We suppose the user is typing the string manually and just
239 // has not finished typing the mail address part.
240 // So we take everything that's left of the '<' as name and the rest as mail
241 for (i = 0; len > i; ++i) {
242 c = aStr[i];
243 if (QLatin1Char('<') != c) {
244 name.append(c);
245 } else {
246 break;
247 }
248 }
249 mail = aStr.mid(i + 1);
250 if (mail.endsWith(QLatin1Char('>'))) {
251 mail.truncate(mail.length() - 1);
252 }
253
254 } else {
255 // Loop backwards until we find the start of the string
256 // or a ',' that is outside of a comment
257 // and outside of quoted text before the leading '<'.
258 bInComment = false;
259 bInQuotesOutsideOfEmail = false;
260 for (i = iAd-1; 0 <= i; --i) {
261 c = aStr[i];
262 if (bInComment) {
263 if (QLatin1Char('(') == c) {
264 if (!name.isEmpty()) {
265 name.prepend(QLatin1Char(' '));
266 }
267 bInComment = false;
268 } else {
269 name.prepend(c); // all comment stuff is part of the name
270 }
271 } else if (bInQuotesOutsideOfEmail) {
272 if (QLatin1Char(cQuotes) == c) {
273 bInQuotesOutsideOfEmail = false;
274 } else if (c != QLatin1Char('\\')) {
275 name.prepend(c);
276 }
277 } else {
278 // found the start of this addressee ?
279 if (QLatin1Char(',') == c) {
280 break;
281 }
282 // stuff is before the leading '<' ?
283 if (iMailStart) {
284 if (QLatin1Char(cQuotes) == c) {
285 bInQuotesOutsideOfEmail = true; // end of quoted text found
286 } else {
287 name.prepend(c);
288 }
289 } else {
290 switch (c.toLatin1()) {
291 case '<':
292 iMailStart = i;
293 break;
294 case ')':
295 if (!name.isEmpty()) {
296 name.prepend(QLatin1Char(' '));
297 }
298 bInComment = true;
299 break;
300 default:
301 if (QLatin1Char(' ') != c) {
302 mail.prepend(c);
303 }
304 }
305 }
306 }
307 }
308
309 name = name.simplified();
310 mail = mail.simplified();
311
312 if (mail.isEmpty()) {
313 return false;
314 }
315
316 mail.append(QLatin1Char('@'));
317
318 // Loop forward until we find the end of the string
319 // or a ',' that is outside of a comment
320 // and outside of quoted text behind the trailing '>'.
321 bInComment = false;
322 bInQuotesOutsideOfEmail = false;
323 int parenthesesNesting = 0;
324 for (i = iAd+1; len > i; ++i) {
325 c = aStr[i];
326 if (bInComment) {
327 if (QLatin1Char(')') == c) {
328 if (--parenthesesNesting == 0) {
329 bInComment = false;
330 if (!name.isEmpty()) {
331 name.append(QLatin1Char(' '));
332 }
333 } else {
334 // nested ")", add it
335 name.append(QLatin1Char(')')); // name can't be empty here
336 }
337 } else {
338 if (QLatin1Char('(') == c) {
339 // nested "("
340 ++parenthesesNesting;
341 }
342 name.append(c); // all comment stuff is part of the name
343 }
344 } else if (bInQuotesOutsideOfEmail) {
345 if (QLatin1Char(cQuotes) == c) {
346 bInQuotesOutsideOfEmail = false;
347 } else if (c != QLatin1Char('\\')) {
348 name.append(c);
349 }
350 } else {
351 // found the end of this addressee ?
352 if (QLatin1Char(',') == c) {
353 break;
354 }
355 // stuff is behind the trailing '>' ?
356 if (iMailEnd) {
357 if (QLatin1Char(cQuotes) == c) {
358 bInQuotesOutsideOfEmail = true; // start of quoted text found
359 } else {
360 name.append(c);
361 }
362 } else {
363 switch (c.toLatin1()) {
364 case '>':
365 iMailEnd = i;
366 break;
367 case '(':
368 if (!name.isEmpty()) {
369 name.append(QLatin1Char(' '));
370 }
371 if (++parenthesesNesting > 0) {
372 bInComment = true;
373 }
374 break;
375 default:
376 if (QLatin1Char(' ') != c) {
377 mail.append(c);
378 }
379 }
380 }
381 }
382 }
383 }
384
385 name = name.simplified();
386 mail = mail.simplified();
387
388 return !(name.isEmpty() || mail.isEmpty());
389}
390
391Person::Ptr Person::fromFullName(const QString &fullName)
392{
393 QString email, name;
394 extractEmailAddressAndName(fullName, email, name);
395 return Person::Ptr(new Person(name, email));
396}
397