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: 97.0% 352 / 0 / 363
Functions: 100.0% 35 / 0 / 35
Branches: 94.2% 407 / 0 / 432

data/ICalendar.cpp
Line Branch Exec Source
1 #include "ICalendar.h"
2
3 #include <QLoggingCategory>
4
5
4/7
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 8 taken 3905 times.
✓ Branch 4 → 5 taken 17 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 17 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
3922 Q_LOGGING_CATEGORY(lcICal, "mailjd.calendar.ical")
6
7 namespace ICal {
8
9 namespace {
10
11 // RFC 6868: a parameter value cannot hold DQUOTE or a line break; they are
12 // written as ^' and ^n, and ^ itself as ^^. S106-128: a DQUOTE written raw
13 // (e.g. in an attendee's CN) ended the quoted value early, and the rest of
14 // the name re-parsed as further parameters (a forged PARTSTAT).
15 9722 QString encodeParameterValue(const QString &value) {
16 9722 QString out;
17
1/1
✓ Branch 3 → 4 taken 9722 times.
9722 out.reserve(value.size());
18
2/2
✓ Branch 66 → 5 taken 53070 times.
✓ Branch 66 → 67 taken 9722 times.
62792 for (int i = 0; i < value.size(); ++i) {
19 53070 const QChar c = value.at(i);
20
3/3
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 17 taken 53068 times.
✓ Branch 13 → 14 taken 2 times.
53072 if (c == QLatin1Char('^')) out += QStringLiteral("^^");
21
3/3
✓ Branch 20 → 21 taken 162 times.
✓ Branch 20 → 28 taken 52906 times.
✓ Branch 24 → 25 taken 162 times.
53230 else if (c == QLatin1Char('"')) out += QStringLiteral("^'");
22
2/8
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 40 taken 52906 times.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 40 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 52906 times.
52906 else if (c == QLatin1Char('\r') && i + 1 < value.size() && value.at(i + 1) == QLatin1Char('\n')) continue;
23
6/7
✓ Branch 46 → 47 taken 52905 times.
✓ Branch 46 → 51 taken 1 time.
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 52905 times.
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 61 taken 52905 times.
✓ Branch 57 → 58 taken 1 time.
52907 else if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) out += QStringLiteral("^n");
24
1/1
✓ Branch 61 → 62 taken 52905 times.
52905 else out += c;
25 }
26 9722 return out;
27 ✗ }
28
29 205859 QString decodeParameterValue(const QString &value) {
30
3/3
✓ Branch 4 → 5 taken 205859 times.
✓ Branch 5 → 6 taken 205710 times.
✓ Branch 5 → 7 taken 149 times.
205859 if (!value.contains(QLatin1Char('^'))) return value;
31 149 QString out;
32
1/1
✓ Branch 8 → 9 taken 149 times.
149 out.reserve(value.size());
33
2/2
✓ Branch 63 → 10 taken 1787 times.
✓ Branch 63 → 64 taken 149 times.
1936 for (int i = 0; i < value.size(); ++i) {
34 1787 const QChar c = value.at(i);
35
2/2
✓ Branch 12 → 13 taken 1782 times.
✓ Branch 12 → 14 taken 5 times.
1787 const QChar next = i + 1 < value.size() ? value.at(i + 1) : QChar();
36
7/7
✓ Branch 18 → 19 taken 296 times.
✓ Branch 18 → 24 taken 1491 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 294 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 30 taken 1785 times.
✓ Branch 28 → 29 taken 2 times.
1787 if (c == QLatin1Char('^') && next == QLatin1Char('^')) { out += QLatin1Char('^'); ++i; }
37
7/7
✓ Branch 33 → 34 taken 294 times.
✓ Branch 33 → 39 taken 1491 times.
✓ Branch 37 → 38 taken 292 times.
✓ Branch 37 → 39 taken 2 times.
✓ Branch 40 → 41 taken 292 times.
✓ Branch 40 → 45 taken 1493 times.
✓ Branch 43 → 44 taken 292 times.
1785 else if (c == QLatin1Char('^') && next == QLatin1Char('\'')) { out += QLatin1Char('"'); ++i; }
38
7/7
✓ Branch 48 → 49 taken 2 times.
✓ Branch 48 → 54 taken 1491 times.
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 54 taken 1 time.
✓ Branch 55 → 56 taken 1 time.
✓ Branch 55 → 60 taken 1492 times.
✓ Branch 58 → 59 taken 1 time.
1493 else if (c == QLatin1Char('^') && next == QLatin1Char('n')) { out += QLatin1Char('\n'); ++i; }
39
1/1
✓ Branch 60 → 61 taken 1492 times.
1492 else out += c; // any other ^ stays literal (RFC 6868 §3)
40 }
41 149 return out;
42 149 }
43
44 // RFC 5545 §3.2: parameter values containing COLON, SEMICOLON or COMMA
45 // must be quoted.
46 9722 QString renderParameterValue(const QString &raw) {
47
1/1
✓ Branch 2 → 3 taken 9722 times.
9722 const QString value = encodeParameterValue(raw);
48
6/6
✓ Branch 5 → 6 taken 9722 times.
✓ Branch 6 → 7 taken 9607 times.
✓ Branch 6 → 15 taken 115 times.
✓ Branch 9 → 10 taken 9607 times.
✓ Branch 10 → 11 taken 9605 times.
✓ Branch 10 → 15 taken 2 times.
19327 if (value.contains(QLatin1Char(':')) || value.contains(QLatin1Char(';')) ||
49
5/5
✓ Branch 13 → 14 taken 9605 times.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 9604 times.
✓ Branch 17 → 18 taken 118 times.
✓ Branch 17 → 27 taken 9604 times.
19327 value.contains(QLatin1Char(',')))
50
2/2
✓ Branch 22 → 23 taken 118 times.
✓ Branch 23 → 24 taken 118 times.
236 return QLatin1Char('"') + value + QLatin1Char('"');
51 9604 return value;
52 9722 }
53
54 205859 QString unquote(const QString &value) {
55
5/5
✓ Branch 3 → 4 taken 191127 times.
✓ Branch 3 → 13 taken 14732 times.
✓ Branch 6 → 7 taken 191127 times.
✓ Branch 7 → 8 taken 4145 times.
✓ Branch 7 → 13 taken 186982 times.
210004 if (value.size() >= 2 && value.startsWith(QLatin1Char('"')) &&
56
5/5
✓ Branch 10 → 11 taken 4145 times.
✓ Branch 11 → 12 taken 3579 times.
✓ Branch 11 → 13 taken 566 times.
✓ Branch 14 → 15 taken 3579 times.
✓ Branch 14 → 17 taken 202280 times.
210004 value.endsWith(QLatin1Char('"')))
57 3579 return value.mid(1, value.size() - 2);
58 202280 return value;
59 }
60
61 // Splits "NAME;P1=a;P2="x:y":value" at the first colon outside quotes.
62 1320727 bool parseContentLine(const QString &line, Property *property) {
63 1320727 bool inQuotes = false;
64 1320727 int colon = -1;
65
2/2
✓ Branch 20 → 3 taken 13592801 times.
✓ Branch 20 → 21 taken 1527 times.
13594328 for (int i = 0; i < line.size(); ++i) {
66 13592801 const QChar c = line.at(i);
67
2/2
✓ Branch 7 → 8 taken 8705 times.
✓ Branch 7 → 9 taken 13584096 times.
13592801 if (c == QLatin1Char('"'))
68 8705 inQuotes = !inQuotes;
69
6/6
✓ Branch 12 → 13 taken 1322882 times.
✓ Branch 12 → 15 taken 12261214 times.
✓ Branch 13 → 14 taken 1319200 times.
✓ Branch 13 → 15 taken 3682 times.
✓ Branch 16 → 17 taken 1319200 times.
✓ Branch 16 → 18 taken 12264896 times.
13584096 else if (c == QLatin1Char(':') && !inQuotes) {
70 1319200 colon = i;
71 1319200 break;
72 }
73 }
74
2/2
✓ Branch 21 → 22 taken 1564 times.
✓ Branch 21 → 23 taken 1319163 times.
1320727 if (colon <= 0)
75 1564 return false;
76
1/1
✓ Branch 23 → 24 taken 1319163 times.
1319163 const QString head = line.left(colon);
77
1/1
✓ Branch 24 → 25 taken 1319163 times.
1319163 property->value = line.mid(colon + 1);
78 1319163 property->originalLine = line;
79 1319163 QStringList parts;
80 1319163 QString current;
81 1319163 inQuotes = false;
82
2/2
✓ Branch 47 → 30 taken 10049409 times.
✓ Branch 47 → 48 taken 1319163 times.
11368572 for (const QChar c : head) {
83
2/2
✓ Branch 33 → 34 taken 8470 times.
✓ Branch 33 → 35 taken 10040939 times.
10049409 if (c == QLatin1Char('"')) {
84 8470 inQuotes = !inQuotes;
85
1/1
✓ Branch 34 → 46 taken 8470 times.
8470 current += c;
86
6/6
✓ Branch 38 → 39 taken 208126 times.
✓ Branch 38 → 41 taken 9832813 times.
✓ Branch 39 → 40 taken 207382 times.
✓ Branch 39 → 41 taken 744 times.
✓ Branch 42 → 43 taken 207382 times.
✓ Branch 42 → 45 taken 9833557 times.
10040939 } else if (c == QLatin1Char(';') && !inQuotes) {
87
1/1
✓ Branch 43 → 44 taken 207382 times.
207382 parts.append(current);
88 207382 current.clear();
89 } else {
90
1/1
✓ Branch 45 → 46 taken 9833557 times.
9833557 current += c;
91 }
92 }
93
1/1
✓ Branch 48 → 49 taken 1319163 times.
1319163 parts.append(current);
94
3/3
✓ Branch 49 → 50 taken 1319163 times.
✓ Branch 50 → 51 taken 1319163 times.
✓ Branch 51 → 52 taken 1319163 times.
1319163 property->name = parts.first().trimmed().toUpper();
95
2/2
✓ Branch 56 → 57 taken 76 times.
✓ Branch 56 → 58 taken 1319087 times.
1319163 if (property->name.isEmpty())
96 76 return false;
97
2/2
✓ Branch 100 → 59 taken 207234 times.
✓ Branch 100 → 101 taken 1319087 times.
1526321 for (int i = 1; i < parts.size(); ++i) {
98 207234 const int eq = parts.at(i).indexOf(QLatin1Char('='));
99 207234 Parameter parameter;
100
2/2
✓ Branch 63 → 64 taken 1375 times.
✓ Branch 63 → 71 taken 205859 times.
207234 if (eq < 0) {
101
2/2
✓ Branch 65 → 66 taken 1375 times.
✓ Branch 66 → 67 taken 1375 times.
1375 parameter.name = parts.at(i).trimmed().toUpper();
102 } else {
103
3/3
✓ Branch 72 → 73 taken 205859 times.
✓ Branch 73 → 74 taken 205859 times.
✓ Branch 74 → 75 taken 205859 times.
205859 parameter.name = parts.at(i).left(eq).trimmed().toUpper();
104
3/3
✓ Branch 80 → 81 taken 205859 times.
✓ Branch 81 → 82 taken 205859 times.
✓ Branch 82 → 83 taken 205859 times.
205859 parameter.value = decodeParameterValue(unquote(parts.at(i).mid(eq + 1)));
105
1/2
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 96 taken 205859 times.
205859 if (parameter.value.isNull())
106 ✗ parameter.value = QStringLiteral(""); // "NAME=" keeps its equals sign
107 }
108
1/1
✓ Branch 96 → 97 taken 207234 times.
207234 property->parameters.append(parameter);
109 207234 }
110 1319087 return true;
111 1319163 }
112
113 // `orphans` counts continuation lines with no line to continue (S106-142):
114 // they are dropped like malformed lines and make the parse lossy.
115 142382 QStringList unfoldLines(const QString &text, int *orphans) {
116 142382 QStringList lines;
117 142382 QString normalized = text;
118
1/1
✓ Branch 9 → 10 taken 142382 times.
284764 normalized.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
119
1/1
✓ Branch 18 → 19 taken 142382 times.
142382 normalized.replace(QLatin1Char('\r'), QLatin1Char('\n'));
120
5/5
✓ Branch 22 → 23 taken 142382 times.
✓ Branch 23 → 24 taken 142382 times.
✓ Branch 24 → 25 taken 142382 times.
✓ Branch 56 → 26 taken 1586871 times.
✓ Branch 56 → 57 taken 142382 times.
1729253 for (const QString &raw : normalized.split(QLatin1Char('\n'))) {
121
7/8
✓ Branch 28 → 29 taken 1444847 times.
✓ Branch 28 → 40 taken 142024 times.
✓ Branch 33 → 34 taken 1440745 times.
✓ Branch 33 → 39 taken 4102 times.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 1440745 times.
✓ Branch 41 → 42 taken 4102 times.
✓ Branch 41 → 51 taken 1582769 times.
1586871 if (!raw.isEmpty() && (raw.at(0) == QLatin1Char(' ') || raw.at(0) == QLatin1Char('\t'))) {
122
2/2
✓ Branch 43 → 44 taken 4101 times.
✓ Branch 43 → 49 taken 1 time.
4102 if (!lines.isEmpty())
123
3/3
✓ Branch 44 → 45 taken 4101 times.
✓ Branch 45 → 46 taken 4101 times.
✓ Branch 46 → 47 taken 4101 times.
4101 lines.last() += raw.mid(1);
124 else
125 1 ++*orphans;
126 4102 continue;
127 }
128
2/2
✓ Branch 52 → 53 taken 1440745 times.
✓ Branch 52 → 54 taken 142024 times.
1582769 if (!raw.isEmpty())
129
1/1
✓ Branch 53 → 54 taken 1440745 times.
1440745 lines.append(raw);
130 142382 }
131 142382 return lines;
132 142382 }
133
134 42039 void serializeInto(const Component &component, QString *out) {
135
3/3
✓ Branch 5 → 6 taken 42039 times.
✓ Branch 6 → 7 taken 42039 times.
✓ Branch 7 → 8 taken 42039 times.
84078 *out += foldLine(QStringLiteral("BEGIN:") + component.name);
136
2/2
✓ Branch 22 → 14 taken 314604 times.
✓ Branch 22 → 23 taken 42039 times.
356643 for (const Property &property : component.properties)
137
3/3
✓ Branch 15 → 16 taken 314604 times.
✓ Branch 16 → 17 taken 314604 times.
✓ Branch 17 → 18 taken 314604 times.
314604 *out += foldLine(property.contentLine());
138
2/2
✓ Branch 29 → 25 taken 9814 times.
✓ Branch 29 → 30 taken 42039 times.
51853 for (const Component &child : component.components)
139
1/1
✓ Branch 26 → 27 taken 9814 times.
9814 serializeInto(child, out);
140
3/3
✓ Branch 33 → 34 taken 42039 times.
✓ Branch 34 → 35 taken 42039 times.
✓ Branch 35 → 36 taken 42039 times.
84078 *out += foldLine(QStringLiteral("END:") + component.name);
141 42039 }
142
143 } // namespace
144
145 // ─── Property ──────────────────────────────────────────────
146
147 488626 QString Property::parameter(const QString &parameterName) const {
148
2/2
✓ Branch 11 → 4 taken 436121 times.
✓ Branch 11 → 12 taken 404913 times.
841034 for (const Parameter &parameter : parameters)
149
2/2
✓ Branch 6 → 7 taken 83713 times.
✓ Branch 6 → 9 taken 352408 times.
436121 if (parameter.name.compare(parameterName, Qt::CaseInsensitive) == 0)
150 83713 return parameter.value;
151 404913 return {};
152 }
153
154 269216 bool Property::hasParameter(const QString &parameterName) const {
155
2/2
✓ Branch 10 → 4 taken 83870 times.
✓ Branch 10 → 11 taken 268338 times.
352208 for (const Parameter &parameter : parameters)
156
2/2
✓ Branch 6 → 7 taken 878 times.
✓ Branch 6 → 8 taken 82992 times.
83870 if (parameter.name.compare(parameterName, Qt::CaseInsensitive) == 0)
157 878 return true;
158 268338 return false;
159 }
160
161 791 void Property::setParameter(const QString &parameterName, const QString &parameterValue) {
162 791 originalLine.clear();
163
1/1
✓ Branch 3 → 4 taken 791 times.
791 const QString upper = parameterName.toUpper();
164
4/4
✓ Branch 4 → 5 taken 791 times.
✓ Branch 5 → 6 taken 791 times.
✓ Branch 14 → 7 taken 336 times.
✓ Branch 14 → 15 taken 747 times.
1083 for (Parameter &parameter : parameters) {
165
2/2
✓ Branch 9 → 10 taken 44 times.
✓ Branch 9 → 12 taken 292 times.
336 if (parameter.name == upper) {
166 44 parameter.value = parameterValue;
167 44 return;
168 }
169 }
170 747 parameters.append({upper, parameterValue});
171 1538 }
172
173 27894 void Property::removeParameter(const QString &parameterName) {
174
1/1
✓ Branch 2 → 3 taken 27894 times.
27894 const QString upper = parameterName.toUpper();
175
1/1
✓ Branch 3 → 4 taken 27894 times.
27894 const auto removed = parameters.removeIf([&upper](const Parameter &parameter) {
176 385 return parameter.name == upper;
177 });
178
2/2
✓ Branch 4 → 5 taken 193 times.
✓ Branch 4 → 6 taken 27701 times.
27894 if (removed > 0)
179 193 originalLine.clear();
180 27894 }
181
182 3772 void Property::setValue(const QString &newValue) {
183
6/6
✓ Branch 3 → 4 taken 1160 times.
✓ Branch 3 → 7 taken 2612 times.
✓ Branch 5 → 6 taken 836 times.
✓ Branch 5 → 7 taken 324 times.
✓ Branch 8 → 9 taken 836 times.
✓ Branch 8 → 10 taken 2936 times.
3772 if (newValue == value && !originalLine.isEmpty())
184 836 return;
185 2936 value = newValue;
186 2936 originalLine.clear();
187 }
188
189 513690 QString Property::contentLine() const {
190
2/2
✓ Branch 3 → 4 taken 394951 times.
✓ Branch 3 → 5 taken 118739 times.
513690 if (!originalLine.isEmpty())
191 394951 return originalLine;
192 118739 QString line = name;
193
2/2
✓ Branch 26 → 8 taken 9722 times.
✓ Branch 26 → 27 taken 118739 times.
128461 for (const Parameter &parameter : parameters) {
194
2/2
✓ Branch 11 → 12 taken 9722 times.
✓ Branch 12 → 13 taken 9722 times.
9722 line += QLatin1Char(';') + parameter.name;
195
1/2
✓ Branch 15 → 16 taken 9722 times.
✗ Branch 15 → 24 not taken.
9722 if (!parameter.value.isNull())
196
3/3
✓ Branch 16 → 17 taken 9722 times.
✓ Branch 19 → 20 taken 9722 times.
✓ Branch 20 → 21 taken 9722 times.
9722 line += QLatin1Char('=') + renderParameterValue(parameter.value);
197 }
198
2/2
✓ Branch 29 → 30 taken 118739 times.
✓ Branch 30 → 31 taken 118739 times.
118739 line += QLatin1Char(':') + value;
199 118739 return line;
200 118739 }
201
202 // ─── Component ─────────────────────────────────────────────
203
204 138286 const Property *Component::property(const QString &propertyName) const {
205
2/2
✓ Branch 10 → 4 taken 480983 times.
✓ Branch 10 → 11 taken 35917 times.
516900 for (const Property &candidate : properties)
206
2/2
✓ Branch 6 → 7 taken 102369 times.
✓ Branch 6 → 8 taken 378614 times.
480983 if (candidate.name == propertyName)
207 102369 return &candidate;
208 35917 return nullptr;
209 }
210
211 5778 Property *Component::property(const QString &propertyName) {
212
4/4
✓ Branch 2 → 3 taken 5778 times.
✓ Branch 3 → 4 taken 5778 times.
✓ Branch 11 → 5 taken 22705 times.
✓ Branch 11 → 12 taken 3737 times.
26442 for (Property &candidate : properties)
213
2/2
✓ Branch 7 → 8 taken 2041 times.
✓ Branch 7 → 9 taken 20664 times.
22705 if (candidate.name == propertyName)
214 2041 return &candidate;
215 3737 return nullptr;
216 }
217
218 10267 QList<Property> Component::propertiesNamed(const QString &propertyName) const {
219 10267 QList<Property> result;
220
2/2
✓ Branch 10 → 4 taken 75078 times.
✓ Branch 10 → 11 taken 10267 times.
85345 for (const Property &candidate : properties)
221
2/2
✓ Branch 6 → 7 taken 2103 times.
✓ Branch 6 → 8 taken 72975 times.
75078 if (candidate.name == propertyName)
222
1/1
✓ Branch 7 → 8 taken 2103 times.
2103 result.append(candidate);
223 10267 return result;
224 ✗ }
225
226 78561 QString Component::value(const QString &propertyName) const {
227 78561 const Property *found = property(propertyName);
228
2/2
✓ Branch 3 → 4 taken 54455 times.
✓ Branch 3 → 5 taken 24106 times.
78561 return found ? found->value : QString();
229 }
230
231 1651 bool Component::hasProperty(const QString &propertyName) const {
232 1651 return property(propertyName) != nullptr;
233 }
234
235 743 void Component::setProperty(const Property &replacement) {
236 743 bool replaced = false;
237
2/2
✓ Branch 13 → 3 taken 3003 times.
✓ Branch 13 → 14 taken 743 times.
3746 for (int i = 0; i < properties.size();) {
238
2/2
✓ Branch 5 → 6 taken 2873 times.
✓ Branch 5 → 7 taken 130 times.
3003 if (properties.at(i).name != replacement.name) {
239 2873 ++i;
240 2873 continue;
241 }
242
1/2
✓ Branch 7 → 8 taken 130 times.
✗ Branch 7 → 11 not taken.
130 if (!replaced) {
243 130 properties[i] = replacement;
244 130 replaced = true;
245 130 ++i;
246 } else {
247 ✗ properties.removeAt(i);
248 }
249 }
250
2/2
✓ Branch 14 → 15 taken 613 times.
✓ Branch 14 → 16 taken 130 times.
743 if (!replaced)
251 613 properties.append(replacement);
252 743 }
253
254 3374 void Component::setPropertyValue(const QString &propertyName, const QString &newValue) {
255
1/1
✓ Branch 2 → 3 taken 3374 times.
3374 Property *existing = property(propertyName);
256
2/2
✓ Branch 3 → 4 taken 1046 times.
✓ Branch 3 → 7 taken 2328 times.
3374 if (existing) {
257 1046 existing->setValue(newValue);
258 // Drop duplicates of a single-valued property.
259 1046 bool seen = false;
260
1/1
✓ Branch 5 → 6 taken 1046 times.
1046 properties.removeIf([&](const Property &candidate) {
261
2/2
✓ Branch 3 → 4 taken 6797 times.
✓ Branch 3 → 5 taken 1046 times.
7843 if (candidate.name != propertyName)
262 6797 return false;
263
1/2
✓ Branch 5 → 6 taken 1046 times.
✗ Branch 5 → 7 not taken.
1046 if (!seen) {
264 1046 seen = true;
265 1046 return false;
266 }
267 ✗ return true;
268 });
269 1046 return;
270 }
271 2328 Property created;
272 2328 created.name = propertyName;
273 2328 created.value = newValue;
274
1/1
✓ Branch 9 → 10 taken 2328 times.
2328 properties.append(created);
275 2328 }
276
277 3151 int Component::removeProperties(const QString &propertyName) {
278 3151 return int(properties.removeIf([&propertyName](const Property &candidate) {
279 12394 return candidate.name == propertyName;
280 3151 }));
281 }
282
283 9143 QList<Component> Component::componentsNamed(const QString &componentName) const {
284 9143 QList<Component> result;
285
2/2
✓ Branch 10 → 4 taken 18541 times.
✓ Branch 10 → 11 taken 9143 times.
27684 for (const Component &child : components)
286
2/2
✓ Branch 6 → 7 taken 2749 times.
✓ Branch 6 → 8 taken 15792 times.
18541 if (child.name == componentName)
287
1/1
✓ Branch 7 → 8 taken 2749 times.
2749 result.append(child);
288 9143 return result;
289 ✗ }
290
291 1548 int Component::removeComponents(const QString &componentName) {
292 1548 return int(components.removeIf([&componentName](const Component &child) {
293 6575 return child.name == componentName;
294 1548 }));
295 }
296
297 // ─── Parse / serialize ─────────────────────────────────────
298
299 142382 bool parse(const QString &text, QList<Component> *components, QString *error, int *skippedLines) {
300
2/2
✓ Branch 2 → 3 taken 106112 times.
✓ Branch 2 → 4 taken 36270 times.
142382 if (skippedLines)
301 106112 *skippedLines = 0;
302
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 14 taken 142382 times.
142382 if (!components) {
303 ✗ if (error) *error = QStringLiteral("No output component list");
304 ✗ return false;
305 }
306
1/1
✓ Branch 14 → 15 taken 142382 times.
142382 components->clear();
307 142382 QList<Component> stack;
308 142382 int orphans = 0;
309
1/1
✓ Branch 15 → 16 taken 142382 times.
142382 const QStringList lines = unfoldLines(text, &orphans);
310
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 31 taken 142381 times.
142382 if (orphans > 0) {
311
7/7
✓ Branch 17 → 18 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 28 → 19 taken 1 time.
✓ Branch 28 → 29 taken 1 time.
2 qCWarning(lcICal) << "Skipping" << orphans << "continuation lines before the first line";
312
1/2
✓ Branch 29 → 30 taken 1 time.
✗ Branch 29 → 31 not taken.
1 if (skippedLines)
313 1 *skippedLines += orphans;
314 }
315
2/2
✓ Branch 157 → 33 taken 1320727 times.
✓ Branch 157 → 158 taken 142164 times.
1462891 for (const QString &line : lines) {
316 1320727 Property property;
317
3/3
✓ Branch 34 → 35 taken 1320727 times.
✓ Branch 35 → 36 taken 1640 times.
✓ Branch 35 → 65 taken 1319087 times.
1320727 if (!parseContentLine(line, &property)) {
318 // Content only at debug level: lines carry calendar and contact data.
319
7/7
✓ Branch 36 → 37 taken 1640 times.
✓ Branch 40 → 41 taken 1640 times.
✓ Branch 41 → 42 taken 1640 times.
✓ Branch 43 → 44 taken 1640 times.
✓ Branch 44 → 45 taken 1640 times.
✓ Branch 48 → 38 taken 1640 times.
✓ Branch 48 → 49 taken 1640 times.
3280 qCWarning(lcICal) << "Skipping malformed content line of" << line.size() << "characters";
320
7/7
✓ Branch 49 → 50 taken 1640 times.
✓ Branch 53 → 54 taken 1640 times.
✓ Branch 54 → 55 taken 1640 times.
✓ Branch 55 → 56 taken 1640 times.
✓ Branch 56 → 57 taken 1640 times.
✓ Branch 61 → 51 taken 1640 times.
✓ Branch 61 → 62 taken 1640 times.
3280 qCDebug(lcICal) << "Malformed line:" << line.left(80);
321
2/2
✓ Branch 62 → 63 taken 1505 times.
✓ Branch 62 → 64 taken 135 times.
1640 if (skippedLines)
322 1505 ++*skippedLines;
323 1640 continue;
324 1640 }
325
2/2
✓ Branch 67 → 68 taken 192726 times.
✓ Branch 67 → 91 taken 1126361 times.
1319087 if (property.name == QLatin1String("BEGIN")) {
326
2/2
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 82 taken 192725 times.
192726 if (stack.size() >= kMaxComponentDepth) {
327
1/2
✓ Branch 70 → 71 taken 1 time.
✗ Branch 70 → 81 not taken.
1 if (error)
328
1/1
✓ Branch 75 → 76 taken 1 time.
2 *error = QStringLiteral("iCalendar components nested deeper than %1 levels").arg(kMaxComponentDepth);
329 1 return false;
330 }
331 192725 Component component;
332
2/2
✓ Branch 82 → 83 taken 192725 times.
✓ Branch 83 → 84 taken 192725 times.
192725 component.name = property.value.trimmed().toUpper();
333
1/1
✓ Branch 87 → 88 taken 192725 times.
192725 stack.append(component);
334 192725 continue;
335 192725 }
336
2/2
✓ Branch 93 → 94 taken 191209 times.
✓ Branch 93 → 130 taken 935152 times.
1126361 if (property.name == QLatin1String("END")) {
337
2/2
✓ Branch 94 → 95 taken 191209 times.
✓ Branch 95 → 96 taken 191209 times.
191209 const QString name = property.value.trimmed().toUpper();
338
7/7
✓ Branch 98 → 99 taken 191153 times.
✓ Branch 98 → 102 taken 56 times.
✓ Branch 99 → 100 taken 191153 times.
✓ Branch 101 → 102 taken 161 times.
✓ Branch 101 → 103 taken 190992 times.
✓ Branch 104 → 105 taken 217 times.
✓ Branch 104 → 117 taken 190992 times.
191209 if (stack.isEmpty() || stack.last().name != name) {
339
2/2
✓ Branch 105 → 106 taken 2 times.
✓ Branch 105 → 116 taken 215 times.
217 if (error)
340
1/1
✓ Branch 110 → 111 taken 2 times.
4 *error = QStringLiteral("Unbalanced END:%1 in iCalendar data").arg(name);
341 217 return false;
342 }
343
1/1
✓ Branch 117 → 118 taken 190992 times.
190992 Component finished = stack.takeLast();
344
2/2
✓ Branch 119 → 120 taken 140837 times.
✓ Branch 119 → 121 taken 50155 times.
190992 if (stack.isEmpty())
345
1/1
✓ Branch 120 → 123 taken 140837 times.
140837 components->append(finished);
346 else
347
2/2
✓ Branch 121 → 122 taken 50155 times.
✓ Branch 122 → 123 taken 50155 times.
50155 stack.last().components.append(finished);
348 190992 continue;
349 382201 }
350
2/2
✓ Branch 131 → 132 taken 641 times.
✓ Branch 131 → 146 taken 934511 times.
935152 if (stack.isEmpty()) {
351
6/6
✓ Branch 132 → 133 taken 641 times.
✓ Branch 136 → 137 taken 641 times.
✓ Branch 137 → 138 taken 641 times.
✓ Branch 138 → 139 taken 641 times.
✓ Branch 142 → 134 taken 641 times.
✓ Branch 142 → 143 taken 641 times.
1282 qCWarning(lcICal) << "Skipping iCalendar property outside a component" << property.name;
352
1/2
✓ Branch 143 → 144 taken 641 times.
✗ Branch 143 → 145 not taken.
641 if (skippedLines)
353 641 ++*skippedLines;
354 641 continue;
355 641 }
356
2/2
✓ Branch 146 → 147 taken 934511 times.
✓ Branch 147 → 148 taken 934511 times.
934511 stack.last().properties.append(property);
357 1320727 }
358
2/2
✓ Branch 159 → 160 taken 1146 times.
✓ Branch 159 → 173 taken 141018 times.
142164 if (!stack.isEmpty()) {
359
2/2
✓ Branch 160 → 161 taken 8 times.
✓ Branch 160 → 172 taken 1138 times.
1146 if (error)
360
2/2
✓ Branch 165 → 166 taken 8 times.
✓ Branch 166 → 167 taken 8 times.
16 *error = QStringLiteral("Missing END:%1 in iCalendar data").arg(stack.last().name);
361 1146 return false;
362 }
363 141018 return true;
364 142382 }
365
366 9489 Component parseFirst(const QString &text, const QString &name, QString *error) {
367 9489 QList<Component> components;
368
3/3
✓ Branch 2 → 3 taken 9489 times.
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 9486 times.
9489 if (!parse(text, &components, error))
369 3 return {};
370
4/4
✓ Branch 5 → 6 taken 9486 times.
✓ Branch 6 → 7 taken 9486 times.
✓ Branch 15 → 8 taken 9317 times.
✓ Branch 15 → 16 taken 171 times.
9488 for (const Component &component : components)
371
2/2
✓ Branch 10 → 11 taken 9315 times.
✓ Branch 10 → 13 taken 2 times.
9317 if (component.name == name)
372 9315 return component;
373
2/2
✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 27 taken 168 times.
171 if (error)
374
1/1
✓ Branch 21 → 22 taken 3 times.
6 *error = QStringLiteral("No %1 component in iCalendar data").arg(name);
375 171 return {};
376 9489 }
377
378 32225 QString serialize(const Component &component) {
379 32225 QString out;
380
1/1
✓ Branch 2 → 3 taken 32225 times.
32225 serializeInto(component, &out);
381 32225 return out;
382 ✗ }
383
384 398736 QString foldLine(const QString &line) {
385
1/1
✓ Branch 2 → 3 taken 398736 times.
398736 const QByteArray utf8 = line.toUtf8();
386
2/2
✓ Branch 4 → 5 taken 395759 times.
✓ Branch 4 → 13 taken 2977 times.
398736 if (utf8.size() <= 75)
387
1/1
✓ Branch 8 → 9 taken 395759 times.
395759 return line + QStringLiteral("\r\n");
388 2977 QByteArray folded;
389 2977 int lineOctets = 0;
390 2977 int limit = 75;
391
2/2
✓ Branch 31 → 14 taken 37632298 times.
✓ Branch 31 → 32 taken 2977 times.
37635275 for (int i = 0; i < utf8.size();) {
392 // Length of the UTF-8 sequence starting at i.
393 37632298 const auto lead = static_cast<unsigned char>(utf8.at(i));
394 37632298 int length = 1;
395
2/2
✓ Branch 15 → 16 taken 33 times.
✓ Branch 15 → 17 taken 37632265 times.
37632298 if (lead >= 0xF0) length = 4;
396
2/2
✓ Branch 17 → 18 taken 262 times.
✓ Branch 17 → 19 taken 37632003 times.
37632265 else if (lead >= 0xE0) length = 3;
397
2/2
✓ Branch 19 → 20 taken 1818 times.
✓ Branch 19 → 21 taken 37630185 times.
37632003 else if (lead >= 0xC0) length = 2;
398 37632298 length = qMin(length, int(utf8.size()) - i);
399
2/2
✓ Branch 23 → 24 taken 507435 times.
✓ Branch 23 → 26 taken 37124863 times.
37632298 if (lineOctets + length > limit) {
400
1/1
✓ Branch 24 → 25 taken 507435 times.
507435 folded += "\r\n ";
401 507435 lineOctets = 0;
402 507435 limit = 74; // the leading space counts towards the 75 octets
403 }
404
2/2
✓ Branch 26 → 27 taken 37632298 times.
✓ Branch 27 → 28 taken 37632298 times.
37632298 folded += utf8.mid(i, length);
405 37632298 lineOctets += length;
406 37632298 i += length;
407 }
408
1/1
✓ Branch 32 → 33 taken 2977 times.
2977 folded += "\r\n";
409
1/1
✓ Branch 33 → 34 taken 2977 times.
2977 return QString::fromUtf8(folded);
410 398736 }
411
412 484591 QString unescapeText(const QString &value) {
413 484591 QString result;
414
1/1
✓ Branch 3 → 4 taken 484591 times.
484591 result.reserve(value.size());
415
2/2
✓ Branch 54 → 5 taken 31031952 times.
✓ Branch 54 → 55 taken 484591 times.
31516543 for (int i = 0; i < value.size(); ++i) {
416 31031952 const QChar c = value.at(i);
417
5/6
✓ Branch 9 → 10 taken 74609 times.
✓ Branch 9 → 13 taken 30957343 times.
✓ Branch 11 → 12 taken 74609 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 74609 times.
✓ Branch 14 → 50 taken 30957343 times.
31031952 if (c == QLatin1Char('\\') && i + 1 < value.size()) {
418 74609 const QChar next = value.at(i + 1);
419
5/6
✓ Branch 19 → 20 taken 13087 times.
✓ Branch 19 → 24 taken 61522 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 13087 times.
✓ Branch 26 → 27 taken 61522 times.
✓ Branch 26 → 31 taken 13087 times.
74609 if (next == QLatin1Char('n') || next == QLatin1Char('N')) {
420
1/1
✓ Branch 29 → 30 taken 61522 times.
61522 result.append(QLatin1Char('\n'));
421 61522 ++i;
422 74604 continue;
423 }
424
8/8
✓ Branch 34 → 35 taken 12950 times.
✓ Branch 34 → 43 taken 137 times.
✓ Branch 38 → 39 taken 195 times.
✓ Branch 38 → 43 taken 12755 times.
✓ Branch 42 → 43 taken 190 times.
✓ Branch 42 → 44 taken 5 times.
✓ Branch 45 → 46 taken 13082 times.
✓ Branch 45 → 48 taken 5 times.
13087 if (next == QLatin1Char('\\') || next == QLatin1Char(',') || next == QLatin1Char(';')) {
425
1/1
✓ Branch 46 → 47 taken 13082 times.
13082 result.append(next);
426 13082 ++i;
427 13082 continue;
428 }
429 }
430
1/1
✓ Branch 50 → 51 taken 30957348 times.
30957348 result.append(c);
431 }
432 484591 return result;
433 ✗ }
434
435 228120 QString escapeText(const QString &value) {
436 228120 QString escaped;
437
1/1
✓ Branch 3 → 4 taken 228120 times.
228120 escaped.reserve(value.size() + 8);
438
2/2
✓ Branch 57 → 6 taken 1836799 times.
✓ Branch 57 → 58 taken 228120 times.
2064919 for (const QChar c : value) {
439
3/3
✓ Branch 9 → 10 taken 62 times.
✓ Branch 9 → 17 taken 1836737 times.
✓ Branch 13 → 14 taken 62 times.
1836861 if (c == QLatin1Char('\\')) escaped += QStringLiteral("\\\\");
440
3/3
✓ Branch 20 → 21 taken 220 times.
✓ Branch 20 → 28 taken 1836517 times.
✓ Branch 24 → 25 taken 220 times.
1836957 else if (c == QLatin1Char(';')) escaped += QStringLiteral("\\;");
441
3/3
✓ Branch 31 → 32 taken 8189 times.
✓ Branch 31 → 39 taken 1828328 times.
✓ Branch 35 → 36 taken 8189 times.
1844706 else if (c == QLatin1Char(',')) escaped += QStringLiteral("\\,");
442
3/3
✓ Branch 42 → 43 taken 6238 times.
✓ Branch 42 → 50 taken 1822090 times.
✓ Branch 46 → 47 taken 6238 times.
1834566 else if (c == QLatin1Char('\n')) escaped += QStringLiteral("\\n");
443
2/2
✓ Branch 53 → 54 taken 28 times.
✓ Branch 53 → 55 taken 1822062 times.
1822090 else if (c == QLatin1Char('\r')) continue;
444
1/1
✓ Branch 55 → 56 taken 1822062 times.
1822062 else escaped += c;
445 }
446 228120 return escaped;
447 ✗ }
448
449 2414 QStringList splitValues(const QString &value) {
450 2414 QStringList result;
451 2414 QString current;
452
2/2
✓ Branch 31 → 3 taken 24907 times.
✓ Branch 31 → 32 taken 2414 times.
27321 for (int i = 0; i < value.size(); ++i) {
453 24907 const QChar c = value.at(i);
454
5/6
✓ Branch 7 → 8 taken 91 times.
✓ Branch 7 → 11 taken 24816 times.
✓ Branch 9 → 10 taken 91 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 91 times.
✓ Branch 12 → 17 taken 24816 times.
24907 if (c == QLatin1Char('\\') && i + 1 < value.size()) {
455
1/1
✓ Branch 13 → 14 taken 91 times.
91 current += c;
456
1/1
✓ Branch 15 → 16 taken 91 times.
91 current += value.at(++i);
457 1041 continue;
458 }
459
2/2
✓ Branch 20 → 21 taken 950 times.
✓ Branch 20 → 26 taken 23866 times.
24816 if (c == QLatin1Char(',')) {
460
2/2
✓ Branch 21 → 22 taken 950 times.
✓ Branch 22 → 23 taken 950 times.
950 result.append(current.trimmed());
461 950 current.clear();
462 950 continue;
463 }
464
1/1
✓ Branch 26 → 27 taken 23866 times.
23866 current += c;
465 }
466
2/3
✓ Branch 32 → 33 taken 2414 times.
✓ Branch 35 → 36 taken 2414 times.
✗ Branch 35 → 40 not taken.
2414 if (!current.trimmed().isEmpty())
467
2/2
✓ Branch 36 → 37 taken 2414 times.
✓ Branch 37 → 38 taken 2414 times.
2414 result.append(current.trimmed());
468 2414 return result;
469 2414 }
470
471 } // namespace ICal
472