MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 98.0% 383 / 0 / 391
Functions: 100.0% 29 / 0 / 29
Branches: 84.7% 581 / 0 / 686

data/VCardCodec.cpp
Line Branch Exec Source
1 #include "VCardCodec.h"
2 #include <algorithm>
3 #include <functional>
4 #include <QRegularExpression>
5 #include "VCardWriter.h"
6
7 #include <QLoggingCategory>
8 #include <QStringDecoder>
9
10
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 1519 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
1522 Q_LOGGING_CATEGORY(lcVCard, "mailjd.contacts.codec")
11
12 namespace VCardCodec {
13
14 namespace {
15
16 const QStringList kEncodingWords{QStringLiteral("QUOTED-PRINTABLE"), QStringLiteral("BASE64"),
17 QStringLiteral("B"), QStringLiteral("8BIT"), QStringLiteral("7BIT")};
18
19 265349 bool isQuotedPrintable(const ICal::Property &property) {
20
3/3
✓ Branch 5 → 6 taken 265349 times.
✓ Branch 11 → 12 taken 176 times.
✓ Branch 11 → 13 taken 265173 times.
530698 if (property.parameter(QStringLiteral("ENCODING")).compare(QLatin1String("QUOTED-PRINTABLE"),
21 265349 Qt::CaseInsensitive) == 0)
22 176 return true;
23
1/1
✓ Branch 16 → 17 taken 265173 times.
265173 return property.hasParameter(QStringLiteral("QUOTED-PRINTABLE"));
24 }
25
26 21695 bool isBase64(const ICal::Property &property) {
27
2/2
✓ Branch 5 → 6 taken 21695 times.
✓ Branch 6 → 7 taken 21695 times.
21695 const QString encoding = property.parameter(QStringLiteral("ENCODING")).toUpper();
28
3/4
✓ Branch 12 → 13 taken 5 times.
✓ Branch 12 → 21 taken 21690 times.
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 21 not taken.
21700 return encoding == QLatin1String("B") || encoding == QLatin1String("BASE64") ||
29
6/11
✓ Branch 19 → 20 taken 5 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 5 times.
✓ Branch 23 → 24 taken 5 times.
✓ Branch 23 → 25 taken 21690 times.
✓ Branch 25 → 26 taken 5 times.
✓ Branch 25 → 27 taken 21690 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
43395 property.hasParameter(QStringLiteral("BASE64"));
30 21695 }
31
32 176 QByteArray decodeQuotedPrintable(const QString &value) {
33
1/1
✓ Branch 2 → 3 taken 176 times.
176 const QByteArray in = value.toLatin1();
34 176 QByteArray out;
35
1/1
✓ Branch 4 → 5 taken 176 times.
176 out.reserve(in.size());
36
2/2
✓ Branch 24 → 6 taken 5244 times.
✓ Branch 24 → 25 taken 176 times.
5420 for (qsizetype i = 0; i < in.size(); ++i) {
37
5/6
✓ Branch 7 → 8 taken 418 times.
✓ Branch 7 → 11 taken 4826 times.
✓ Branch 9 → 10 taken 418 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 418 times.
✓ Branch 12 → 20 taken 4826 times.
5244 if (in.at(i) == '=' && i + 2 < in.size()) {
38 418 bool ok = false;
39
2/2
✓ Branch 13 → 14 taken 418 times.
✓ Branch 14 → 15 taken 418 times.
418 const int byte = in.mid(i + 1, 2).toInt(&ok, 16);
40
1/2
✓ Branch 16 → 17 taken 418 times.
✗ Branch 16 → 19 not taken.
418 if (ok) {
41
1/1
✓ Branch 17 → 18 taken 418 times.
418 out.append(char(byte));
42 418 i += 2;
43 418 continue;
44 }
45 }
46
1/1
✓ Branch 21 → 22 taken 4826 times.
4826 out.append(in.at(i));
47 }
48 176 return out;
49 176 }
50
51 // The value of a 2.1 quoted-printable property as text in its CHARSET.
52 265344 QString decodedText(const ICal::Property &property) {
53
3/3
✓ Branch 2 → 3 taken 265344 times.
✓ Branch 3 → 4 taken 265168 times.
✓ Branch 3 → 5 taken 176 times.
265344 if (!isQuotedPrintable(property))
54 265168 return property.value;
55
1/1
✓ Branch 5 → 6 taken 176 times.
176 const QByteArray bytes = decodeQuotedPrintable(property.value);
56
1/1
✓ Branch 9 → 10 taken 176 times.
176 QString charset = property.parameter(QStringLiteral("CHARSET"));
57
2/2
✓ Branch 13 → 14 taken 37 times.
✓ Branch 13 → 21 taken 139 times.
176 if (charset.isEmpty())
58 37 charset = QStringLiteral("UTF-8");
59
2/2
✓ Branch 22 → 23 taken 176 times.
✓ Branch 25 → 26 taken 176 times.
176 QStringDecoder decoder(charset.toLatin1().constData());
60
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 31 taken 176 times.
176 if (!decoder.isValid())
61 ✗ return QString::fromUtf8(bytes);
62
1/1
✓ Branch 32 → 33 taken 176 times.
176 return decoder.decode(bytes);
63 176 }
64
65 // Splits a structured value (N, ADR, ORG) at unescaped semicolons and
66 // unescapes each component.
67 48878 QStringList structured(const QString &value) {
68 48878 QStringList parts;
69 48878 QString current;
70
2/2
✓ Branch 31 → 3 taken 558687 times.
✓ Branch 31 → 32 taken 48878 times.
607565 for (qsizetype i = 0; i < value.size(); ++i) {
71 558687 const QChar c = value.at(i);
72
5/6
✓ Branch 7 → 8 taken 57 times.
✓ Branch 7 → 11 taken 558630 times.
✓ Branch 9 → 10 taken 57 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 57 times.
✓ Branch 12 → 17 taken 558630 times.
558687 if (c == QLatin1Char('\\') && i + 1 < value.size()) {
73
1/1
✓ Branch 13 → 14 taken 57 times.
57 current += c;
74
1/1
✓ Branch 15 → 16 taken 57 times.
57 current += value.at(++i);
75 195675 continue;
76 }
77
2/2
✓ Branch 20 → 21 taken 195618 times.
✓ Branch 20 → 26 taken 363012 times.
558630 if (c == QLatin1Char(';')) {
78
2/2
✓ Branch 21 → 22 taken 195618 times.
✓ Branch 22 → 23 taken 195618 times.
195618 parts.append(ICal::unescapeText(current));
79 195618 current.clear();
80 195618 continue;
81 }
82
1/1
✓ Branch 26 → 27 taken 363012 times.
363012 current += c;
83 }
84
2/2
✓ Branch 32 → 33 taken 48878 times.
✓ Branch 33 → 34 taken 48878 times.
48878 parts.append(ICal::unescapeText(current));
85 48878 return parts;
86 48878 }
87
88 163519 QString textValue(const ICal::Property &property) {
89
2/2
✓ Branch 2 → 3 taken 163519 times.
✓ Branch 3 → 4 taken 163519 times.
163519 QString text = ICal::unescapeText(decodedText(property));
90
1/1
✓ Branch 11 → 12 taken 163519 times.
327038 text.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
91 163519 return text;
92 ✗ }
93
94 // TYPE values in lower case: TYPE=a,b, repeated TYPE params and the bare
95 // parameters of vCard 2.1 ("TEL;HOME;VOICE").
96 48586 QStringList typesOf(const ICal::Property &property, bool *preferred) {
97 48586 QStringList types;
98 48586 bool pref = false;
99
2/2
✓ Branch 67 → 4 taken 13591 times.
✓ Branch 67 → 68 taken 48586 times.
62177 for (const ICal::Parameter &parameter : property.parameters) {
100 13591 QStringList words;
101
2/2
✓ Branch 7 → 8 taken 12100 times.
✓ Branch 7 → 15 taken 1491 times.
13591 if (parameter.name == QLatin1String("TYPE"))
102
1/1
✓ Branch 11 → 12 taken 12100 times.
12100 words = parameter.value.split(QLatin1Char(','), Qt::SkipEmptyParts);
103
5/6
✓ Branch 16 → 17 taken 671 times.
✓ Branch 16 → 20 taken 820 times.
✓ Branch 18 → 19 taken 671 times.
✗ Branch 18 → 20 not taken.
✓ Branch 21 → 22 taken 671 times.
✓ Branch 21 → 28 taken 820 times.
1491 else if (parameter.value.isNull() && !kEncodingWords.contains(parameter.name))
104
2/4
✓ Branch 25 → 26 taken 671 times.
✓ Branch 25 → 27 taken 671 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
1342 words = {parameter.name};
105
2/2
✓ Branch 30 → 31 taken 193 times.
✓ Branch 30 → 32 taken 627 times.
820 else if (parameter.name == QLatin1String("PREF"))
106 193 pref = true;
107
4/4
✓ Branch 32 → 33 taken 13591 times.
✓ Branch 33 → 34 taken 13591 times.
✓ Branch 63 → 35 taken 12978 times.
✓ Branch 63 → 64 taken 13591 times.
26569 for (QString word : words) {
108
2/2
✓ Branch 37 → 38 taken 12978 times.
✓ Branch 38 → 39 taken 12978 times.
12978 word = word.trimmed().toLower();
109
2/2
✓ Branch 44 → 45 taken 301 times.
✓ Branch 44 → 46 taken 12677 times.
12978 if (word == QLatin1String("pref"))
110 301 pref = true;
111
7/8
✓ Branch 47 → 48 taken 12673 times.
✓ Branch 47 → 57 taken 4 times.
✓ Branch 50 → 51 taken 11993 times.
✓ Branch 50 → 57 taken 680 times.
✓ Branch 53 → 54 taken 11993 times.
✗ Branch 53 → 57 not taken.
✓ Branch 58 → 59 taken 11993 times.
✓ Branch 58 → 60 taken 684 times.
24670 else if (!word.isEmpty() && word != QLatin1String("internet") && word != QLatin1String("x400") &&
112
1/2
✓ Branch 55 → 56 taken 11993 times.
✗ Branch 55 → 57 not taken.
11993 !types.contains(word))
113
1/1
✓ Branch 59 → 60 taken 11993 times.
11993 types.append(word);
114 12978 }
115 13591 }
116
1/1
✓ Branch 68 → 69 taken 48586 times.
48586 types.sort();
117
1/2
✓ Branch 69 → 70 taken 48586 times.
✗ Branch 69 → 71 not taken.
48586 if (preferred)
118 48586 *preferred = pref;
119 48586 return types;
120 671 }
121
122 48586 QString labelFor(const ICal::Component &card, const ICal::Property &property) {
123
1/1
✓ Branch 2 → 3 taken 48586 times.
48586 const QString group = groupOf(property);
124
2/2
✓ Branch 4 → 5 taken 48249 times.
✓ Branch 4 → 6 taken 337 times.
48586 if (group.isEmpty())
125 48249 return {};
126
2/2
✓ Branch 43 → 8 taken 3047 times.
✓ Branch 43 → 44 taken 63 times.
3110 for (const ICal::Property &candidate : card.properties) {
127
11/16
✓ Branch 9 → 10 taken 3047 times.
✓ Branch 11 → 12 taken 662 times.
✓ Branch 11 → 16 taken 2385 times.
✓ Branch 13 → 14 taken 662 times.
✓ Branch 15 → 16 taken 388 times.
✓ Branch 15 → 17 taken 274 times.
✓ Branch 18 → 19 taken 662 times.
✓ Branch 18 → 20 taken 2385 times.
✓ Branch 20 → 21 taken 3047 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 2773 times.
✓ Branch 22 → 24 taken 274 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
3047 if (groupOf(candidate) != group || baseName(candidate) != QLatin1String("X-ABLABEL"))
128 2773 continue;
129
1/1
✓ Branch 24 → 25 taken 274 times.
274 QString label = textValue(candidate);
130
7/8
✓ Branch 26 → 27 taken 274 times.
✓ Branch 27 → 28 taken 104 times.
✓ Branch 27 → 32 taken 170 times.
✓ Branch 29 → 30 taken 104 times.
✓ Branch 30 → 31 taken 104 times.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 104 times.
✓ Branch 33 → 39 taken 170 times.
274 if (label.startsWith(QLatin1String("_$!<")) && label.endsWith(QLatin1String(">!$_")))
131
1/1
✓ Branch 35 → 36 taken 104 times.
104 label = label.mid(4, label.size() - 8);
132 274 return label;
133 274 }
134 63 return {};
135 48586 }
136
137 44216 ContactValue valueOf(const ICal::Component &card, int index, bool phone) {
138 44216 const ICal::Property &property = card.properties.at(index);
139 44216 ContactValue result;
140
2/2
✓ Branch 3 → 4 taken 44216 times.
✓ Branch 4 → 5 taken 44216 times.
44216 result.value = textValue(property).trimmed();
141
7/7
✓ Branch 8 → 9 taken 3314 times.
✓ Branch 8 → 13 taken 40902 times.
✓ Branch 10 → 11 taken 3314 times.
✓ Branch 11 → 12 taken 42 times.
✓ Branch 11 → 13 taken 3272 times.
✓ Branch 14 → 15 taken 42 times.
✓ Branch 14 → 19 taken 44174 times.
44216 if (phone && result.value.startsWith(QLatin1String("tel:"), Qt::CaseInsensitive))
142
1/1
✓ Branch 15 → 16 taken 42 times.
42 result.value = result.value.mid(4);
143
1/1
✓ Branch 19 → 20 taken 44216 times.
44216 result.types = typesOf(property, &result.preferred);
144
1/1
✓ Branch 22 → 23 taken 44216 times.
44216 result.label = labelFor(card, property);
145 44216 result.sourceIndex = index;
146 44216 return result;
147 ✗ }
148
149 4370 ContactAddress addressOf(const ICal::Component &card, int index) {
150 4370 const ICal::Property &property = card.properties.at(index);
151
2/2
✓ Branch 3 → 4 taken 4370 times.
✓ Branch 4 → 5 taken 4370 times.
4370 const QStringList parts = structured(decodedText(property));
152 4370 ContactAddress address;
153
1/1
✓ Branch 6 → 7 taken 4370 times.
4370 address.poBox = parts.value(0);
154
1/1
✓ Branch 9 → 10 taken 4370 times.
4370 address.extended = parts.value(1);
155
1/1
✓ Branch 12 → 13 taken 4370 times.
4370 address.street = parts.value(2);
156
1/1
✓ Branch 15 → 16 taken 4370 times.
4370 address.locality = parts.value(3);
157
1/1
✓ Branch 18 → 19 taken 4370 times.
4370 address.region = parts.value(4);
158
1/1
✓ Branch 21 → 22 taken 4370 times.
4370 address.postalCode = parts.value(5);
159
1/1
✓ Branch 24 → 25 taken 4370 times.
4370 address.country = parts.value(6);
160
1/1
✓ Branch 27 → 28 taken 4370 times.
4370 address.types = typesOf(property, &address.preferred);
161
1/1
✓ Branch 30 → 31 taken 4370 times.
4370 address.label = labelFor(card, property);
162 4370 address.sourceIndex = index;
163 4370 return address;
164 4370 }
165
166 1736 PartialDate dateOf(const ICal::Property &property) {
167
2/2
✓ Branch 2 → 3 taken 1736 times.
✓ Branch 3 → 4 taken 1736 times.
1736 QString text = textValue(property).trimmed();
168 1736 const int time = text.indexOf(QLatin1Char('T'));
169
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 13 taken 1735 times.
1736 if (time > 0)
170
1/1
✓ Branch 9 → 10 taken 1 time.
1 text = text.left(time);
171
1/1
✓ Branch 13 → 14 taken 1736 times.
1736 PartialDate date = PartialDate::fromIso(text);
172 // Apple: a placeholder year flagged as omitted (D-2 "Year unknown").
173
3/3
✓ Branch 17 → 18 taken 1736 times.
✓ Branch 20 → 21 taken 155 times.
✓ Branch 20 → 22 taken 1581 times.
1736 if (property.hasParameter(QStringLiteral("X-APPLE-OMIT-YEAR")))
174 155 date.year = 0;
175 1736 return date;
176 1736 }
177
178 11060 ContactPhoto photoOf(const ICal::Property &property) {
179 11060 ContactPhoto photo;
180
1/1
✓ Branch 2 → 3 taken 11060 times.
11060 const QString value = property.value.trimmed();
181
2/2
✓ Branch 6 → 7 taken 11060 times.
✓ Branch 7 → 8 taken 11060 times.
11060 const QString valueType = property.parameter(QStringLiteral("VALUE")).toLower();
182
3/3
✓ Branch 12 → 13 taken 11060 times.
✓ Branch 13 → 14 taken 149 times.
✓ Branch 13 → 36 taken 10911 times.
11060 if (value.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
183 149 const int comma = value.indexOf(QLatin1Char(','));
184
1/1
✓ Branch 17 → 18 taken 149 times.
149 const QString header = value.mid(5, comma - 5);
185 149 photo.kind = ContactPhoto::Kind::Inline;
186
2/2
✓ Branch 21 → 22 taken 149 times.
✓ Branch 22 → 23 taken 149 times.
149 photo.mimeType = header.section(QLatin1Char(';'), 0, 0).toLower();
187
3/3
✓ Branch 27 → 28 taken 149 times.
✓ Branch 28 → 29 taken 149 times.
✓ Branch 29 → 30 taken 149 times.
149 photo.data = QByteArray::fromBase64(value.mid(comma + 1).toLatin1());
188 149 return photo;
189 149 }
190
1/2
✓ Branch 41 → 42 taken 10850 times.
✗ Branch 41 → 48 not taken.
21761 if (valueType == QLatin1String("uri") || valueType == QLatin1String("url") ||
191
4/5
✓ Branch 38 → 39 taken 10850 times.
✓ Branch 38 → 48 taken 61 times.
✓ Branch 43 → 44 taken 10850 times.
✓ Branch 44 → 45 taken 10850 times.
✗ Branch 44 → 48 not taken.
32611 value.startsWith(QLatin1String("http:"), Qt::CaseInsensitive) ||
192
5/5
✓ Branch 46 → 47 taken 10850 times.
✓ Branch 47 → 48 taken 2 times.
✓ Branch 47 → 49 taken 10848 times.
✓ Branch 50 → 51 taken 63 times.
✓ Branch 50 → 53 taken 10848 times.
21761 value.startsWith(QLatin1String("https:"), Qt::CaseInsensitive)) {
193 63 photo.kind = ContactPhoto::Kind::Uri;
194 63 photo.uri = value;
195 63 return photo;
196 }
197 // S106-165: a URI without VALUE=uri (cid:, file:, …) is no base64 — the
198 // base64 alphabet has no ':'. It is a URI photo (fetched only from the
199 // book's own origin, D-15) instead of inline garbage.
200
4/7
✓ Branch 53 → 54 taken 9 times.
✓ Branch 53 → 66 taken 10839 times.
✓ Branch 55 → 56 taken 9 times.
✗ Branch 55 → 66 not taken.
✓ Branch 60 → 61 taken 9 times.
✗ Branch 178 → 179 not taken.
✗ Branch 178 → 180 not taken.
10857 static const QRegularExpression scheme(QStringLiteral("^[A-Za-z][A-Za-z0-9+.-]*:"));
201
13/16
✓ Branch 66 → 67 taken 10848 times.
✓ Branch 67 → 68 taken 3 times.
✓ Branch 67 → 75 taken 10845 times.
✓ Branch 68 → 69 taken 3 times.
✓ Branch 69 → 70 taken 3 times.
✗ Branch 69 → 75 not taken.
✓ Branch 71 → 72 taken 3 times.
✓ Branch 72 → 73 taken 3 times.
✓ Branch 73 → 74 taken 1 time.
✓ Branch 73 → 75 taken 2 times.
✓ Branch 76 → 77 taken 3 times.
✓ Branch 76 → 78 taken 10845 times.
✓ Branch 78 → 79 taken 1 time.
✓ Branch 78 → 81 taken 10847 times.
✗ Branch 181 → 182 not taken.
✗ Branch 181 → 183 not taken.
10851 if (!isBase64(property) && !isQuotedPrintable(property) && scheme.match(value).hasMatch()) {
202 1 photo.kind = ContactPhoto::Kind::Uri;
203 1 photo.uri = value;
204 1 return photo;
205 }
206 10847 photo.kind = ContactPhoto::Kind::Inline;
207
8/13
✓ Branch 81 → 82 taken 10847 times.
✓ Branch 82 → 83 taken 2 times.
✓ Branch 82 → 85 taken 10845 times.
✓ Branch 83 → 84 taken 2 times.
✓ Branch 84 → 85 taken 2 times.
✗ Branch 84 → 89 not taken.
✓ Branch 86 → 87 taken 10847 times.
✓ Branch 87 → 88 taken 10847 times.
✗ Branch 89 → 90 not taken.
✓ Branch 92 → 93 taken 10847 times.
✗ Branch 92 → 94 not taken.
✗ Branch 186 → 187 not taken.
✗ Branch 186 → 188 not taken.
32541 photo.data = isBase64(property) || !isQuotedPrintable(property) ? QByteArray::fromBase64(value.toLatin1())
208 10847 : decodeQuotedPrintable(value);
209
2/2
✓ Branch 97 → 98 taken 10847 times.
✓ Branch 98 → 99 taken 10847 times.
10847 QString type = property.parameter(QStringLiteral("TYPE")).toLower();
210
1/2
✗ Branch 103 → 104 not taken.
✓ Branch 103 → 115 taken 10847 times.
10847 if (type.isEmpty())
211 ✗ type = property.parameter(QStringLiteral("MEDIATYPE")).toLower();
212
6/7
✓ Branch 116 → 117 taken 10847 times.
✗ Branch 116 → 122 not taken.
✓ Branch 119 → 120 taken 10847 times.
✓ Branch 120 → 121 taken 10796 times.
✓ Branch 120 → 122 taken 51 times.
✓ Branch 123 → 124 taken 10796 times.
✓ Branch 123 → 133 taken 51 times.
10847 if (!type.isEmpty() && !type.contains(QLatin1Char('/')))
213
1/1
✓ Branch 127 → 128 taken 10796 times.
10796 type = QStringLiteral("image/") + type;
214 10847 photo.mimeType = type;
215 10847 return photo;
216 11060 }
217
218 72182 void readSingles(const ICal::Component &tree, ContactCard *card) {
219
2/2
✓ Branch 221 → 4 taken 436911 times.
✓ Branch 221 → 222 taken 72182 times.
509093 for (const ICal::Property &property : tree.properties) {
220
1/1
✓ Branch 5 → 6 taken 436911 times.
436911 const QString name = baseName(property);
221
6/6
✓ Branch 8 → 9 taken 47564 times.
✓ Branch 8 → 12 taken 389347 times.
✓ Branch 10 → 11 taken 47521 times.
✓ Branch 10 → 12 taken 43 times.
✓ Branch 13 → 14 taken 47521 times.
✓ Branch 13 → 20 taken 389390 times.
436911 if (name == QLatin1String("FN") && card->formattedName.isEmpty()) {
222
2/2
✓ Branch 14 → 15 taken 47521 times.
✓ Branch 15 → 16 taken 47521 times.
47521 card->formattedName = textValue(property).trimmed();
223
6/6
✓ Branch 22 → 23 taken 42260 times.
✓ Branch 22 → 26 taken 347130 times.
✓ Branch 24 → 25 taken 42257 times.
✓ Branch 24 → 26 taken 3 times.
✓ Branch 27 → 28 taken 42257 times.
✓ Branch 27 → 50 taken 347133 times.
389390 } else if (name == QLatin1String("N") && card->name.isEmpty()) {
224
2/2
✓ Branch 28 → 29 taken 42257 times.
✓ Branch 29 → 30 taken 42257 times.
42257 const QStringList parts = structured(decodedText(property));
225
1/1
✓ Branch 31 → 32 taken 42257 times.
84514 card->name = {parts.value(0).trimmed(), parts.value(1).trimmed(), parts.value(2).trimmed(),
226 42257 parts.value(3).trimmed(), parts.value(4).trimmed()};
227
5/6
✓ Branch 52 → 53 taken 66709 times.
✓ Branch 52 → 56 taken 280424 times.
✓ Branch 54 → 55 taken 66709 times.
✗ Branch 54 → 56 not taken.
✓ Branch 57 → 58 taken 66709 times.
✓ Branch 57 → 64 taken 280424 times.
389390 } else if (name == QLatin1String("UID") && card->uid.isEmpty()) {
228
2/2
✓ Branch 58 → 59 taken 66709 times.
✓ Branch 59 → 60 taken 66709 times.
66709 card->uid = textValue(property).trimmed();
229
5/6
✓ Branch 66 → 67 taken 97 times.
✓ Branch 66 → 70 taken 280327 times.
✓ Branch 68 → 69 taken 97 times.
✗ Branch 68 → 70 not taken.
✓ Branch 71 → 72 taken 97 times.
✓ Branch 71 → 78 taken 280327 times.
280424 } else if (name == QLatin1String("NICKNAME") && card->nickname.isEmpty()) {
230
2/2
✓ Branch 72 → 73 taken 97 times.
✓ Branch 73 → 74 taken 97 times.
97 card->nickname = textValue(property).trimmed();
231
5/6
✓ Branch 80 → 81 taken 2251 times.
✓ Branch 80 → 84 taken 278076 times.
✓ Branch 82 → 83 taken 2251 times.
✗ Branch 82 → 84 not taken.
✓ Branch 85 → 86 taken 2251 times.
✓ Branch 85 → 101 taken 278076 times.
280327 } else if (name == QLatin1String("ORG") && card->organization.isEmpty()) {
232
2/2
✓ Branch 86 → 87 taken 2251 times.
✓ Branch 87 → 88 taken 2251 times.
2251 const QStringList parts = structured(decodedText(property));
233
2/2
✓ Branch 89 → 90 taken 2251 times.
✓ Branch 90 → 91 taken 2251 times.
2251 card->organization = parts.value(0).trimmed();
234
2/2
✓ Branch 94 → 95 taken 2251 times.
✓ Branch 95 → 96 taken 2251 times.
2251 card->department = parts.value(1).trimmed();
235
5/6
✓ Branch 103 → 104 taken 1337 times.
✓ Branch 103 → 107 taken 276739 times.
✓ Branch 105 → 106 taken 1337 times.
✗ Branch 105 → 107 not taken.
✓ Branch 108 → 109 taken 1337 times.
✓ Branch 108 → 115 taken 276739 times.
280327 } else if (name == QLatin1String("TITLE") && card->title.isEmpty()) {
236
2/2
✓ Branch 109 → 110 taken 1337 times.
✓ Branch 110 → 111 taken 1337 times.
1337 card->title = textValue(property).trimmed();
237
5/6
✓ Branch 117 → 118 taken 1022 times.
✓ Branch 117 → 121 taken 275717 times.
✓ Branch 119 → 120 taken 1022 times.
✗ Branch 119 → 121 not taken.
✓ Branch 122 → 123 taken 1022 times.
✓ Branch 122 → 127 taken 275717 times.
276739 } else if (name == QLatin1String("NOTE") && card->note.isEmpty()) {
238
1/1
✓ Branch 123 → 124 taken 1022 times.
1022 card->note = textValue(property);
239
5/6
✓ Branch 129 → 130 taken 1453 times.
✓ Branch 129 → 133 taken 274264 times.
✓ Branch 131 → 132 taken 1453 times.
✗ Branch 131 → 133 not taken.
✓ Branch 134 → 135 taken 1453 times.
✓ Branch 134 → 136 taken 274264 times.
275717 } else if (name == QLatin1String("BDAY") && !card->birthday.isValid()) {
240
1/1
✓ Branch 135 → 218 taken 1453 times.
1453 card->birthday = dateOf(property);
241
5/6
✓ Branch 138 → 139 taken 273981 times.
✓ Branch 138 → 142 taken 283 times.
✗ Branch 141 → 142 not taken.
✓ Branch 141 → 145 taken 273981 times.
✓ Branch 146 → 147 taken 283 times.
✓ Branch 146 → 148 taken 273981 times.
274547 } else if ((name == QLatin1String("ANNIVERSARY") || name == QLatin1String("X-ANNIVERSARY")) &&
242
1/2
✓ Branch 143 → 144 taken 283 times.
✗ Branch 143 → 145 not taken.
283 !card->anniversary.isValid()) {
243
1/1
✓ Branch 147 → 218 taken 283 times.
283 card->anniversary = dateOf(property);
244
6/6
✓ Branch 150 → 151 taken 273807 times.
✓ Branch 150 → 154 taken 174 times.
✓ Branch 153 → 154 taken 76 times.
✓ Branch 153 → 155 taken 273731 times.
✓ Branch 156 → 157 taken 250 times.
✓ Branch 156 → 173 taken 273731 times.
273981 } else if (name == QLatin1String("KIND") || name == QLatin1String("X-ADDRESSBOOKSERVER-KIND")) {
245
3/3
✓ Branch 157 → 158 taken 250 times.
✓ Branch 158 → 159 taken 250 times.
✓ Branch 159 → 160 taken 250 times.
250 const QString kind = textValue(property).trimmed().toLower();
246
5/6
✓ Branch 163 → 164 taken 1 time.
✓ Branch 163 → 167 taken 249 times.
✗ Branch 166 → 167 not taken.
✓ Branch 166 → 168 taken 1 time.
✓ Branch 169 → 170 taken 249 times.
✓ Branch 169 → 171 taken 1 time.
250 if (!(card->isFamily() && kind == QLatin1String("individual")))
247 249 card->kind = kind;
248
2/2
✓ Branch 175 → 176 taken 65 times.
✓ Branch 175 → 200 taken 273666 times.
273981 } else if (name == QLatin1String("X-MAILJD-KIND")) {
249 // A family's own entry (one vCard per family, its members on it): an
250 // individual card for every other client, "family" for MailJD.
251
6/14
✓ Branch 176 → 177 taken 65 times.
✓ Branch 177 → 178 taken 65 times.
✓ Branch 180 → 181 taken 65 times.
✗ Branch 180 → 187 not taken.
✓ Branch 182 → 183 taken 65 times.
✗ Branch 182 → 186 not taken.
✓ Branch 190 → 191 taken 65 times.
✗ Branch 190 → 192 not taken.
✓ Branch 192 → 193 taken 65 times.
✗ Branch 192 → 218 not taken.
✗ Branch 297 → 298 not taken.
✗ Branch 297 → 299 not taken.
✗ Branch 301 → 302 not taken.
✗ Branch 301 → 303 not taken.
260 if (textValue(property).trimmed().compare(QLatin1String("family"), Qt::CaseInsensitive) == 0 &&
252
2/4
✓ Branch 185 → 186 taken 65 times.
✗ Branch 185 → 187 not taken.
✓ Branch 188 → 189 taken 65 times.
✗ Branch 188 → 190 not taken.
195 (card->kind.isEmpty() || card->kind == QLatin1String("individual")))
253 65 card->kind = QStringLiteral("family");
254
5/6
✓ Branch 202 → 203 taken 11060 times.
✓ Branch 202 → 205 taken 262606 times.
✓ Branch 203 → 204 taken 11060 times.
✗ Branch 203 → 205 not taken.
✓ Branch 206 → 207 taken 11060 times.
✓ Branch 206 → 211 taken 262606 times.
273666 } else if (name == QLatin1String("PHOTO") && card->photo.kind == ContactPhoto::Kind::None) {
255
1/1
✓ Branch 207 → 208 taken 11060 times.
11060 card->photo = photoOf(property);
256
2/2
✓ Branch 213 → 214 taken 72094 times.
✓ Branch 213 → 218 taken 190512 times.
262606 } else if (name == QLatin1String("VERSION")) {
257
1/1
✓ Branch 214 → 215 taken 72094 times.
72094 card->version = property.value.trimmed();
258 }
259 436911 }
260 114439 }
261
262 72182 void readMultiples(const ICal::Component &tree, ContactCard *card) {
263
2/2
✓ Branch 78 → 3 taken 436911 times.
✓ Branch 78 → 79 taken 72182 times.
509093 for (int i = 0; i < tree.properties.size(); ++i) {
264 436911 const ICal::Property &property = tree.properties.at(i);
265
1/1
✓ Branch 4 → 5 taken 436911 times.
436911 const QString name = baseName(property);
266
2/2
✓ Branch 7 → 8 taken 40250 times.
✓ Branch 7 → 12 taken 396661 times.
436911 if (name == QLatin1String("EMAIL")) {
267
2/2
✓ Branch 8 → 9 taken 40250 times.
✓ Branch 9 → 10 taken 40250 times.
40250 card->emails.append(valueOf(tree, i, false));
268
2/2
✓ Branch 14 → 15 taken 3314 times.
✓ Branch 14 → 19 taken 393347 times.
396661 } else if (name == QLatin1String("TEL")) {
269
2/2
✓ Branch 15 → 16 taken 3314 times.
✓ Branch 16 → 17 taken 3314 times.
3314 card->phones.append(valueOf(tree, i, true));
270
2/2
✓ Branch 21 → 22 taken 652 times.
✓ Branch 21 → 26 taken 392695 times.
393347 } else if (name == QLatin1String("URL")) {
271
2/2
✓ Branch 22 → 23 taken 652 times.
✓ Branch 23 → 24 taken 652 times.
652 card->urls.append(valueOf(tree, i, false));
272
2/2
✓ Branch 28 → 29 taken 4370 times.
✓ Branch 28 → 33 taken 388325 times.
392695 } else if (name == QLatin1String("ADR")) {
273
2/2
✓ Branch 29 → 30 taken 4370 times.
✓ Branch 30 → 31 taken 4370 times.
4370 card->addresses.append(addressOf(tree, i));
274
2/2
✓ Branch 35 → 36 taken 1555 times.
✓ Branch 35 → 60 taken 386770 times.
388325 } else if (name == QLatin1String("CATEGORIES")) {
275
6/6
✓ Branch 36 → 37 taken 1555 times.
✓ Branch 37 → 38 taken 1555 times.
✓ Branch 39 → 40 taken 1555 times.
✓ Branch 40 → 41 taken 1555 times.
✓ Branch 57 → 42 taken 2237 times.
✓ Branch 57 → 58 taken 1555 times.
3792 for (const QString &category : ICal::splitValues(decodedText(property))) {
276
2/2
✓ Branch 43 → 44 taken 2237 times.
✓ Branch 44 → 45 taken 2237 times.
2237 const QString text = ICal::unescapeText(category).trimmed();
277
3/6
✓ Branch 47 → 48 taken 2237 times.
✗ Branch 47 → 51 not taken.
✓ Branch 49 → 50 taken 2237 times.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 2237 times.
✗ Branch 52 → 54 not taken.
2237 if (!text.isEmpty() && !card->categories.contains(text))
278
1/1
✓ Branch 53 → 54 taken 2237 times.
2237 card->categories.append(text);
279 3792 }
280
6/6
✓ Branch 62 → 63 taken 386595 times.
✓ Branch 62 → 66 taken 175 times.
✓ Branch 65 → 66 taken 117 times.
✓ Branch 65 → 67 taken 386478 times.
✓ Branch 68 → 69 taken 292 times.
✓ Branch 68 → 75 taken 386478 times.
386770 } else if (name == QLatin1String("MEMBER") || name == QLatin1String("X-ADDRESSBOOKSERVER-MEMBER")) {
281
3/3
✓ Branch 69 → 70 taken 292 times.
✓ Branch 70 → 71 taken 292 times.
✓ Branch 71 → 72 taken 292 times.
292 card->members.append(textValue(property).trimmed());
282 }
283
284 436911 }
285 72182 }
286
287 } // namespace
288
289 // ─── content-line helpers ───────────────────────────────────
290
291 51392 QString decodedValue(const ICal::Property &property) { return decodedText(property); }
292
293 2567219 QString baseName(const ICal::Property &property) {
294 2567219 const int dot = property.name.lastIndexOf(QLatin1Char('.'));
295
2/2
✓ Branch 5 → 6 taken 2490852 times.
✓ Branch 5 → 7 taken 76367 times.
2567219 return dot < 0 ? property.name : property.name.mid(dot + 1);
296 }
297
298 228274 QString groupOf(const ICal::Property &property) {
299 228274 const int dot = property.name.lastIndexOf(QLatin1Char('.'));
300
2/2
✓ Branch 5 → 6 taken 195167 times.
✓ Branch 5 → 7 taken 33107 times.
228274 return dot < 0 ? QString() : property.name.left(dot);
301 }
302
303 103409 bool prepare(const QString &raw, QString *prepared, QString *error) {
304
8/11
✓ Branch 3 → 4 taken 103408 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 4 → 5 taken 103408 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 103408 times.
✓ Branch 9 → 10 taken 103408 times.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 24 taken 103408 times.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
103409 if (raw.size() > MaxCardBytes || raw.toUtf8().size() > MaxCardBytes) {
305
1/2
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 23 not taken.
1 if (error)
306
1/1
✓ Branch 17 → 18 taken 1 time.
2 *error = QStringLiteral("The card is larger than %1 bytes").arg(MaxCardBytes);
307 1 return false;
308 }
309 103408 QString normalized = raw;
310
1/1
✓ Branch 31 → 32 taken 103408 times.
206816 normalized.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
311
1/1
✓ Branch 40 → 41 taken 103408 times.
103408 normalized.replace(QLatin1Char('\r'), QLatin1Char('\n'));
312
1/1
✓ Branch 44 → 45 taken 103408 times.
103408 const QStringList physical = normalized.split(QLatin1Char('\n'));
313 103408 QStringList lines;
314 103408 bool joining = false;
315
2/2
✓ Branch 114 → 47 taken 4862662 times.
✓ Branch 114 → 115 taken 103407 times.
4966069 for (const QString &line : physical) {
316
2/2
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 62 taken 4862661 times.
4862662 if (line.size() > MaxPhysicalLineBytes) {
317
1/2
✓ Branch 50 → 51 taken 1 time.
✗ Branch 50 → 61 not taken.
1 if (error)
318
1/1
✓ Branch 55 → 56 taken 1 time.
2 *error = QStringLiteral("A line of the card is longer than %1 characters").arg(MaxPhysicalLineBytes);
319 1 return false;
320 }
321
5/6
✓ Branch 62 → 63 taken 81 times.
✓ Branch 62 → 66 taken 4862580 times.
✓ Branch 64 → 65 taken 81 times.
✗ Branch 64 → 66 not taken.
✓ Branch 67 → 68 taken 81 times.
✓ Branch 67 → 72 taken 4862580 times.
4862661 if (joining && !lines.isEmpty()) {
322 // vCard 2.1 quoted-printable soft break: "…=" + next physical line.
323
2/2
✓ Branch 68 → 69 taken 81 times.
✓ Branch 69 → 70 taken 81 times.
81 lines.last().chop(1);
324
2/2
✓ Branch 70 → 71 taken 81 times.
✓ Branch 71 → 95 taken 81 times.
81 lines.last() += line;
325
7/8
✓ Branch 73 → 74 taken 4759540 times.
✓ Branch 73 → 87 taken 103040 times.
✓ Branch 78 → 79 taken 810823 times.
✓ Branch 78 → 84 taken 3948717 times.
✗ Branch 83 → 84 not taken.
✓ Branch 83 → 87 taken 810823 times.
✓ Branch 88 → 89 taken 3948717 times.
✓ Branch 88 → 94 taken 913863 times.
8811297 } else if (!line.isEmpty() && (line.at(0) == QLatin1Char(' ') || line.at(0) == QLatin1Char('\t')) &&
326
1/2
✓ Branch 85 → 86 taken 3948717 times.
✗ Branch 85 → 87 not taken.
3948717 !lines.isEmpty()) {
327
3/3
✓ Branch 89 → 90 taken 3948717 times.
✓ Branch 90 → 91 taken 3948717 times.
✓ Branch 91 → 92 taken 3948717 times.
3948717 lines.last() += line.mid(1);
328 } else {
329
1/1
✓ Branch 94 → 95 taken 913863 times.
913863 lines.append(line);
330 }
331
1/1
✓ Branch 95 → 96 taken 4862661 times.
4862661 const QString &current = lines.last();
332 4862661 const int colon = current.indexOf(QLatin1Char(':'));
333
6/8
✓ Branch 99 → 100 taken 4736351 times.
✓ Branch 99 → 109 taken 126310 times.
✓ Branch 100 → 101 taken 4736351 times.
✓ Branch 102 → 103 taken 4736351 times.
✓ Branch 103 → 104 taken 398 times.
✓ Branch 103 → 109 taken 4735953 times.
✗ Branch 184 → 185 not taken.
✗ Branch 184 → 186 not taken.
9725720 joining = colon > 0 && current.left(colon).contains(QLatin1String("QUOTED-PRINTABLE"), Qt::CaseInsensitive) &&
334
5/5
✓ Branch 106 → 107 taken 398 times.
✓ Branch 107 → 108 taken 81 times.
✓ Branch 107 → 109 taken 317 times.
✓ Branch 110 → 111 taken 4736351 times.
✓ Branch 110 → 112 taken 126310 times.
4863059 current.endsWith(QLatin1Char('='));
335 }
336
1/1
✓ Branch 116 → 117 taken 103407 times.
103407 lines.removeAll(QString());
337
2/2
✓ Branch 124 → 125 taken 103407 times.
✓ Branch 125 → 126 taken 103407 times.
206814 *prepared = lines.join(QStringLiteral("\r\n")) + QStringLiteral("\r\n");
338 103407 return true;
339 103408 }
340
341 103409 bool parseTree(const QString &raw, ICal::Component *card, QString *error, int *skippedLines) {
342 103409 QString prepared;
343
3/3
✓ Branch 2 → 3 taken 103409 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 103407 times.
103409 if (!prepare(raw, &prepared, error))
344 2 return false;
345 103407 QList<ICal::Component> components;
346 103407 int skipped = 0;
347
3/3
✓ Branch 5 → 6 taken 103407 times.
✓ Branch 6 → 7 taken 1358 times.
✓ Branch 6 → 8 taken 102049 times.
103407 if (!ICal::parse(prepared, &components, error, &skipped))
348 1358 return false;
349
2/2
✓ Branch 8 → 9 taken 102048 times.
✓ Branch 8 → 10 taken 1 time.
102049 if (skippedLines)
350 102048 *skippedLines = skipped;
351
4/4
✓ Branch 10 → 11 taken 102049 times.
✓ Branch 11 → 12 taken 102049 times.
✓ Branch 40 → 13 taken 102031 times.
✓ Branch 40 → 41 taken 18 times.
102049 for (const ICal::Component &component : components) {
352
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 102031 times.
102031 if (component.name != QLatin1String("VCARD"))
353 ✗ continue;
354 // S106-150: the depth of the deepest branch, not of the first-child
355 // chain (a later sibling could nest far deeper). ICal::parse already
356 // bounds the recursion (kMaxComponentDepth).
357 306133 const std::function<int(const ICal::Component &)> depthOf = [&](const ICal::Component &node) {
358 102071 int deepest = 0;
359
2/2
✓ Branch 9 → 4 taken 40 times.
✓ Branch 9 → 10 taken 102071 times.
102111 for (const ICal::Component &child : node.components)
360
1/1
✓ Branch 5 → 6 taken 40 times.
40 deepest = std::max(deepest, depthOf(child));
361 102071 return deepest + 1;
362 102031 };
363
1/1
✓ Branch 19 → 20 taken 102031 times.
102031 const int depth = depthOf(component);
364
6/6
✓ Branch 20 → 21 taken 102029 times.
✓ Branch 20 → 23 taken 2 times.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 102028 times.
✓ Branch 25 → 26 taken 3 times.
✓ Branch 25 → 35 taken 102028 times.
102031 if (depth > MaxNestingDepth || component.properties.size() > MaxProperties) {
365
1/2
✓ Branch 26 → 27 taken 3 times.
✗ Branch 26 → 34 not taken.
3 if (error)
366 3 *error = QStringLiteral("The card is nested too deeply or has too many properties");
367 3 return false;
368 }
369 102028 *card = component;
370 102028 return true;
371 102031 }
372
2/2
✓ Branch 41 → 42 taken 3 times.
✓ Branch 41 → 49 taken 15 times.
18 if (error)
373 3 *error = QStringLiteral("No vCard found");
374 18 return false;
375 103409 }
376
377 73555 bool parse(const QString &raw, ContactCard *card, QString *error, bool *writable) {
378 73555 ICal::Component tree;
379 73555 int skipped = 0;
380
3/3
✓ Branch 2 → 3 taken 73555 times.
✓ Branch 3 → 4 taken 1373 times.
✓ Branch 3 → 20 taken 72182 times.
73555 if (!parseTree(raw, &tree, error, &skipped)) {
381
8/8
✓ Branch 4 → 5 taken 1373 times.
✓ Branch 8 → 9 taken 1373 times.
✓ Branch 9 → 10 taken 1373 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 1368 times.
✓ Branch 13 → 14 taken 1373 times.
✓ Branch 18 → 6 taken 1373 times.
✓ Branch 18 → 19 taken 1373 times.
2746 qCWarning(lcVCard) << "Unreadable card:" << (error ? *error : QString());
382 1373 return false;
383 }
384 144364 *card = ContactCard{};
385
1/1
✓ Branch 26 → 27 taken 72182 times.
72182 readSingles(tree, card);
386
1/1
✓ Branch 27 → 28 taken 72182 times.
72182 readMultiples(tree, card);
387
1/1
✓ Branch 28 → 29 taken 72182 times.
72182 readPeople(tree, card); // Sprint 103 (VCardPeople.cpp)
388
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 38 taken 72182 times.
72182 if (card->kind.isEmpty())
389 ✗ card->kind = QStringLiteral("individual");
390
2/2
✓ Branch 38 → 39 taken 32834 times.
✓ Branch 38 → 40 taken 39348 times.
72182 if (writable)
391 32834 *writable = skipped == 0;
392
2/2
✓ Branch 40 → 41 taken 149 times.
✓ Branch 40 → 54 taken 72033 times.
72182 if (skipped > 0)
393
7/7
✓ Branch 41 → 42 taken 149 times.
✓ Branch 45 → 46 taken 149 times.
✓ Branch 46 → 47 taken 149 times.
✓ Branch 47 → 48 taken 149 times.
✓ Branch 48 → 49 taken 149 times.
✓ Branch 52 → 43 taken 149 times.
✓ Branch 52 → 53 taken 149 times.
298 qCWarning(lcVCard) << "Card read with" << skipped << "skipped lines; it stays read-only";
394 72182 return true;
395 73555 }
396
397 12 QStringList splitCards(const QString &text) {
398 12 QStringList cards;
399 12 QString normalized = text;
400
1/1
✓ Branch 9 → 10 taken 12 times.
24 normalized.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
401 12 QStringList current;
402 12 int depth = 0;
403
5/5
✓ Branch 17 → 18 taken 12 times.
✓ Branch 18 → 19 taken 12 times.
✓ Branch 19 → 20 taken 12 times.
✓ Branch 58 → 21 taken 20391 times.
✓ Branch 58 → 59 taken 12 times.
20403 for (const QString &line : normalized.split(QLatin1Char('\n'))) {
404
2/2
✓ Branch 22 → 23 taken 20391 times.
✓ Branch 23 → 24 taken 20391 times.
20391 const QString upper = line.trimmed().toUpper();
405
2/2
✓ Branch 27 → 28 taken 5074 times.
✓ Branch 27 → 29 taken 15317 times.
20391 if (upper == QLatin1String("BEGIN:VCARD"))
406 5074 ++depth;
407
2/2
✓ Branch 29 → 30 taken 20379 times.
✓ Branch 29 → 31 taken 12 times.
20391 if (depth > 0)
408
1/1
✓ Branch 30 → 31 taken 20379 times.
20379 current.append(line);
409
7/8
✓ Branch 33 → 34 taken 5074 times.
✓ Branch 33 → 37 taken 15317 times.
✓ Branch 34 → 35 taken 5074 times.
✗ Branch 34 → 37 not taken.
✓ Branch 35 → 36 taken 5073 times.
✓ Branch 35 → 37 taken 1 time.
✓ Branch 38 → 39 taken 5073 times.
✓ Branch 38 → 55 taken 15318 times.
20391 if (upper == QLatin1String("END:VCARD") && depth > 0 && --depth == 0) {
410
3/3
✓ Branch 45 → 46 taken 5073 times.
✓ Branch 46 → 47 taken 5073 times.
✓ Branch 47 → 48 taken 5073 times.
10146 cards.append(current.join(QStringLiteral("\r\n")) + QStringLiteral("\r\n"));
411
1/1
✓ Branch 54 → 55 taken 5073 times.
5073 current.clear();
412 }
413 20403 }
414 12 return cards;
415 12 }
416
417 2767 QString decodeBytes(const QByteArray &bytes) {
418 2767 QStringDecoder utf8(QStringDecoder::Utf8, QStringDecoder::Flag::Stateless);
419
1/1
✓ Branch 3 → 4 taken 2767 times.
2767 const QString text = utf8.decode(bytes);
420
1/2
✓ Branch 5 → 6 taken 2767 times.
✗ Branch 5 → 8 not taken.
2767 if (!utf8.hasError())
421 2767 return text;
422 ✗ QStringDecoder windows("windows-1252");
423 ✗ return windows.isValid() ? windows.decode(bytes) : QString::fromLatin1(bytes);
424 2767 }
425
426 } // namespace VCardCodec
427
428 // ─── value types ────────────────────────────────────────────
429
430 5616 bool ContactAddress::isEmpty() const {
431
9/10
✓ Branch 5 → 6 taken 5457 times.
✓ Branch 5 → 17 taken 129 times.
✓ Branch 7 → 8 taken 583 times.
✓ Branch 7 → 17 taken 4874 times.
✓ Branch 9 → 10 taken 39 times.
✓ Branch 9 → 17 taken 544 times.
✓ Branch 11 → 12 taken 39 times.
✗ Branch 11 → 17 not taken.
✓ Branch 13 → 14 taken 21 times.
✓ Branch 13 → 17 taken 18 times.
11241 return poBox.isEmpty() && extended.isEmpty() && street.isEmpty() && locality.isEmpty() && region.isEmpty() &&
432
4/4
✓ Branch 3 → 4 taken 5586 times.
✓ Branch 3 → 17 taken 30 times.
✓ Branch 15 → 16 taken 3 times.
✓ Branch 15 → 17 taken 18 times.
11241 postalCode.isEmpty() && country.isEmpty();
433 }
434
435 5005 QString ContactAddress::oneLine() const {
436 5005 QStringList parts;
437
2/2
✓ Branch 16 → 7 taken 15015 times.
✓ Branch 16 → 17 taken 5005 times.
35035 for (const QString &part : {street, extended, poBox})
438
3/3
✓ Branch 7 → 8 taken 15015 times.
✓ Branch 10 → 11 taken 4136 times.
✓ Branch 10 → 15 taken 10879 times.
15015 if (!part.trimmed().isEmpty())
439
4/6
✓ Branch 11 → 12 taken 4136 times.
✓ Branch 12 → 13 taken 4136 times.
✓ Branch 18 → 19 taken 15015 times.
✓ Branch 18 → 22 taken 5005 times.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 77 not taken.
24156 parts.append(part.trimmed());
440
5/5
✓ Branch 23 → 24 taken 5005 times.
✓ Branch 26 → 27 taken 5005 times.
✓ Branch 27 → 28 taken 5005 times.
✓ Branch 28 → 29 taken 5005 times.
✓ Branch 29 → 30 taken 5005 times.
5005 const QString city = (postalCode.trimmed() + QLatin1Char(' ') + locality.trimmed()).trimmed();
441
2/2
✓ Branch 35 → 36 taken 4970 times.
✓ Branch 35 → 37 taken 35 times.
5005 if (!city.isEmpty())
442
1/1
✓ Branch 36 → 37 taken 4970 times.
4970 parts.append(city);
443
2/2
✓ Branch 50 → 41 taken 10010 times.
✓ Branch 50 → 51 taken 5005 times.
25025 for (const QString &part : {region, country})
444
3/3
✓ Branch 41 → 42 taken 10010 times.
✓ Branch 44 → 45 taken 1317 times.
✓ Branch 44 → 49 taken 8693 times.
10010 if (!part.trimmed().isEmpty())
445
4/6
✓ Branch 45 → 46 taken 1317 times.
✓ Branch 46 → 47 taken 1317 times.
✓ Branch 52 → 53 taken 10010 times.
✓ Branch 52 → 56 taken 5005 times.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 103 not taken.
16332 parts.append(part.trimmed());
446
1/1
✓ Branch 60 → 61 taken 5005 times.
10010 return parts.join(QStringLiteral(", "));
447 5005 }
448
449 42272 bool ContactName::isEmpty() const {
450
6/10
✓ Branch 3 → 4 taken 42269 times.
✓ Branch 3 → 13 taken 3 times.
✓ Branch 5 → 6 taken 42269 times.
✗ Branch 5 → 13 not taken.
✓ Branch 7 → 8 taken 42269 times.
✗ Branch 7 → 13 not taken.
✓ Branch 9 → 10 taken 42269 times.
✗ Branch 9 → 13 not taken.
✓ Branch 11 → 12 taken 42269 times.
✗ Branch 11 → 13 not taken.
42272 return family.isEmpty() && given.isEmpty() && additional.isEmpty() && prefix.isEmpty() && suffix.isEmpty();
451 }
452
453 2553 QString PartialDate::toIso() const {
454
2/2
✓ Branch 3 → 4 taken 1480 times.
✓ Branch 3 → 5 taken 1073 times.
2553 if (!isValid())
455 1480 return {};
456
2/2
✓ Branch 10 → 11 taken 1073 times.
✓ Branch 13 → 14 taken 1073 times.
1073 const QString monthDay = QStringLiteral("%1-%2").arg(month, 2, 10, QLatin1Char('0')).arg(day, 2, 10, QLatin1Char('0'));
457
5/11
✓ Branch 24 → 25 taken 857 times.
✓ Branch 37 → 38 taken 857 times.
✓ Branch 37 → 39 taken 216 times.
✓ Branch 39 → 40 taken 857 times.
✓ Branch 39 → 41 taken 216 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
3003 return hasYear() ? QStringLiteral("%1-").arg(year, 4, 10, QLatin1Char('0')) + monthDay
458
10/14
✓ Branch 18 → 19 taken 857 times.
✓ Branch 18 → 26 taken 216 times.
✓ Branch 25 → 30 taken 857 times.
✓ Branch 29 → 30 taken 216 times.
✓ Branch 31 → 32 taken 216 times.
✓ Branch 31 → 33 taken 857 times.
✓ Branch 33 → 34 taken 216 times.
✓ Branch 33 → 35 taken 857 times.
✓ Branch 35 → 36 taken 857 times.
✓ Branch 35 → 37 taken 216 times.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
3219 : QStringLiteral("--") + monthDay;
459 1073 }
460
461 6265 PartialDate PartialDate::fromIso(const QString &input) {
462 6265 PartialDate date;
463
1/1
✓ Branch 2 → 3 taken 6265 times.
6265 QString text = input.trimmed();
464
3/3
✓ Branch 4 → 5 taken 6265 times.
✓ Branch 5 → 6 taken 350 times.
✓ Branch 5 → 21 taken 5915 times.
6265 if (text.startsWith(QLatin1String("--"))) {
465
1/1
✓ Branch 6 → 7 taken 350 times.
350 QString digits = text.mid(2);
466
1/1
✓ Branch 9 → 10 taken 350 times.
350 digits.remove(QLatin1Char('-'));
467
2/2
✓ Branch 11 → 12 taken 349 times.
✓ Branch 11 → 19 taken 1 time.
350 if (digits.size() >= 4) {
468
2/2
✓ Branch 12 → 13 taken 349 times.
✓ Branch 13 → 14 taken 349 times.
349 date.month = digits.left(2).toInt();
469
2/2
✓ Branch 15 → 16 taken 349 times.
✓ Branch 16 → 17 taken 349 times.
349 date.day = digits.mid(2, 2).toInt();
470 }
471 350 } else {
472 5915 QString digits = text;
473
1/1
✓ Branch 24 → 25 taken 5915 times.
5915 digits.remove(QLatin1Char('-'));
474
2/2
✓ Branch 26 → 27 taken 3710 times.
✓ Branch 26 → 37 taken 2205 times.
5915 if (digits.size() >= 8) {
475
2/2
✓ Branch 27 → 28 taken 3710 times.
✓ Branch 28 → 29 taken 3710 times.
3710 date.year = digits.left(4).toInt();
476
2/2
✓ Branch 30 → 31 taken 3710 times.
✓ Branch 31 → 32 taken 3710 times.
3710 date.month = digits.mid(4, 2).toInt();
477
2/2
✓ Branch 33 → 34 taken 3710 times.
✓ Branch 34 → 35 taken 3710 times.
3710 date.day = digits.mid(6, 2).toInt();
478 }
479 5915 }
480
2/2
✓ Branch 40 → 41 taken 2337 times.
✓ Branch 40 → 42 taken 3928 times.
6265 if (!date.isValid())
481 2337 return {};
482 3928 return date;
483 6265 }
484