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: 88.1% 290 / 0 / 329
Functions: 93.3% 14 / 0 / 15
Branches: 78.6% 578 / 0 / 735

data/ComposeHtmlImport.cpp
Line Branch Exec Source
1 #include "ComposeHtmlImport.h"
2
3 #include <QRegularExpression>
4
5 #include "ComposeDocumentFormat.h"
6
7 #include <QTextBlock>
8 #include <QTextCursor>
9 #include <QTextDocument>
10 #include <QTextFrame>
11 #include <QTextImageFormat>
12 #include <QTextList>
13 #include <QTextTable>
14 #include <QUuid>
15 #include <QVector>
16
17 namespace {
18
19 using namespace ComposeDocumentFormat;
20
21 // No resource is ever loaded while importing (remote, cid or file).
22 class ResourceFreeDocument : public QTextDocument {
23 protected:
24 ✗ QVariant loadResource(int, const QUrl &) override { return QByteArray(); }
25 };
26
27 constexpr qreal ParagraphGap = 6.0; // a margin at least this large reads as a blank line
28 constexpr qreal IndentStep = 40.0; // MailHtmlWriter writes 40 px per indent level
29
30 3864 int remappedHeading(int level) {
31
2/2
✓ Branch 2 → 3 taken 3799 times.
✓ Branch 2 → 4 taken 65 times.
3864 if (level <= 0) return 0;
32
2/2
✓ Branch 4 → 5 taken 48 times.
✓ Branch 4 → 6 taken 17 times.
65 if (level <= 2) return 1; // h1, h2 (MailHtmlWriter writes level 1 as h2)
33
2/2
✓ Branch 6 → 7 taken 5 times.
✓ Branch 6 → 8 taken 12 times.
17 if (level == 3) return 2;
34 12 return 3;
35 }
36
37 98 QTextListFormat::Style allowedListStyle(QTextListFormat::Style style) {
38
1/2
✓ Branch 2 → 3 taken 98 times.
✗ Branch 2 → 4 not taken.
98 switch (style) {
39 98 case QTextListFormat::ListDisc:
40 case QTextListFormat::ListCircle:
41 case QTextListFormat::ListSquare:
42 case QTextListFormat::ListDecimal:
43 case QTextListFormat::ListLowerAlpha:
44 case QTextListFormat::ListUpperAlpha:
45 case QTextListFormat::ListLowerRoman:
46 case QTextListFormat::ListUpperRoman:
47 98 return style;
48 ✗ default:
49 ✗ return QTextListFormat::ListDisc;
50 }
51 }
52
53 3864 bool inHeaderRow(const QTextBlock &block) {
54
1/1
✓ Branch 2 → 3 taken 3864 times.
3864 QTextCursor cursor(block);
55
3/3
✓ Branch 3 → 4 taken 3864 times.
✓ Branch 4 → 5 taken 314 times.
✓ Branch 4 → 19 taken 3550 times.
3864 if (QTextTable *table = cursor.currentTable()) {
56
1/1
✓ Branch 5 → 6 taken 314 times.
314 const QTextTableCell cell = table->cellAt(cursor);
57
6/11
✓ Branch 7 → 8 taken 314 times.
✗ Branch 7 → 13 not taken.
✓ Branch 8 → 9 taken 314 times.
✓ Branch 9 → 10 taken 314 times.
✓ Branch 10 → 11 taken 314 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 314 times.
✓ Branch 15 → 16 taken 314 times.
✗ Branch 15 → 17 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
314 return cell.isValid() && cell.row() < table->format().headerRowCount();
58 314 }
59 3550 return false;
60 3864 }
61
62 15067 QTextCharFormat cleanCharFormat(const QTextCharFormat &source, bool heading, bool codeBlock, bool headerCell,
63 const QHash<QString, QString> &alternativeTexts) {
64
3/3
✓ Branch 2 → 3 taken 15067 times.
✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 36 taken 15056 times.
15067 if (source.isImageFormat()) {
65
1/1
✓ Branch 4 → 5 taken 11 times.
11 const QTextImageFormat image = source.toImageFormat();
66
1/1
✓ Branch 5 → 6 taken 11 times.
11 QTextImageFormat clean;
67
2/2
✓ Branch 6 → 7 taken 11 times.
✓ Branch 7 → 8 taken 11 times.
11 clean.setName(image.name());
68
5/5
✓ Branch 9 → 10 taken 11 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 13 taken 6 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
11 if (image.width() > 0) clean.setWidth(image.width());
69
2/5
✓ Branch 13 → 14 taken 11 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 17 taken 11 times.
✗ Branch 15 → 16 not taken.
✗ Branch 16 → 17 not taken.
11 if (image.height() > 0) clean.setHeight(image.height());
70
1/1
✓ Branch 17 → 18 taken 11 times.
11 const QString alt = image.hasProperty(ImageAltProperty) ? image.stringProperty(ImageAltProperty)
71
3/8
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 11 times.
✗ Branch 19 → 22 not taken.
✓ Branch 20 → 21 taken 11 times.
✓ Branch 22 → 23 taken 11 times.
✗ Branch 22 → 24 not taken.
✗ Branch 203 → 204 not taken.
✗ Branch 203 → 205 not taken.
11 : alternativeTexts.value(image.name());
72
2/3
✓ Branch 25 → 26 taken 11 times.
✗ Branch 25 → 30 not taken.
✓ Branch 27 → 28 taken 11 times.
11 if (!alt.isEmpty()) clean.setProperty(ImageAltProperty, alt);
73
1/1
✓ Branch 30 → 31 taken 11 times.
11 return clean;
74 11 }
75
1/1
✓ Branch 36 → 37 taken 15056 times.
15056 QTextCharFormat clean;
76
8/11
✓ Branch 37 → 38 taken 15056 times.
✓ Branch 38 → 39 taken 2215 times.
✓ Branch 38 → 43 taken 12841 times.
✓ Branch 39 → 40 taken 2215 times.
✓ Branch 40 → 41 taken 2215 times.
✓ Branch 41 → 42 taken 2215 times.
✗ Branch 41 → 43 not taken.
✓ Branch 44 → 45 taken 2215 times.
✓ Branch 44 → 46 taken 12841 times.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
15056 const bool anchor = source.isAnchor() && isAllowedLink(source.anchorHref());
77
9/10
✓ Branch 46 → 47 taken 15056 times.
✓ Branch 47 → 48 taken 2020 times.
✓ Branch 47 → 51 taken 13036 times.
✓ Branch 48 → 49 taken 1957 times.
✓ Branch 48 → 51 taken 63 times.
✓ Branch 49 → 50 taken 1957 times.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 1957 times.
✓ Branch 52 → 54 taken 13099 times.
✓ Branch 53 → 54 taken 1957 times.
15056 if (source.fontWeight() >= 600 && !heading && !headerCell) clean.setFontWeight(QFont::Bold);
78
4/4
✓ Branch 54 → 55 taken 15056 times.
✓ Branch 55 → 56 taken 1804 times.
✓ Branch 55 → 57 taken 13252 times.
✓ Branch 56 → 57 taken 1804 times.
15056 if (source.fontItalic()) clean.setFontItalic(true);
79
8/8
✓ Branch 57 → 58 taken 15056 times.
✓ Branch 58 → 59 taken 2217 times.
✓ Branch 58 → 61 taken 12839 times.
✓ Branch 59 → 60 taken 4 times.
✓ Branch 59 → 61 taken 2213 times.
✓ Branch 62 → 63 taken 4 times.
✓ Branch 62 → 64 taken 15052 times.
✓ Branch 63 → 64 taken 4 times.
15056 if (source.fontUnderline() && !anchor) clean.setFontUnderline(true);
80
4/4
✓ Branch 64 → 65 taken 15056 times.
✓ Branch 65 → 66 taken 3 times.
✓ Branch 65 → 67 taken 15053 times.
✓ Branch 66 → 67 taken 3 times.
15056 if (source.fontStrikeOut()) clean.setFontStrikeOut(true);
81
3/5
✓ Branch 67 → 68 taken 15056 times.
✓ Branch 68 → 69 taken 15056 times.
✗ Branch 68 → 71 not taken.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 76 taken 15056 times.
30112 if (source.verticalAlignment() == QTextCharFormat::AlignSuperScript ||
82
2/3
✓ Branch 69 → 70 taken 15056 times.
✗ Branch 70 → 71 not taken.
✓ Branch 70 → 72 taken 15056 times.
15056 source.verticalAlignment() == QTextCharFormat::AlignSubScript)
83 ✗ clean.setVerticalAlignment(source.verticalAlignment());
84
2/2
✓ Branch 76 → 77 taken 2215 times.
✓ Branch 76 → 82 taken 12841 times.
15056 if (anchor) {
85
1/1
✓ Branch 77 → 78 taken 2215 times.
2215 clean.setAnchor(true);
86
2/2
✓ Branch 78 → 79 taken 2215 times.
✓ Branch 79 → 80 taken 2215 times.
2215 clean.setAnchorHref(source.anchorHref());
87 }
88
2/2
✓ Branch 82 → 83 taken 15056 times.
✓ Branch 83 → 84 taken 15056 times.
15056 const bool codeMarker = source.fontFamilies().toStringList().contains(CodeMarkerFamily, Qt::CaseInsensitive);
89
10/12
✓ Branch 87 → 88 taken 15038 times.
✓ Branch 87 → 94 taken 18 times.
✓ Branch 88 → 89 taken 15038 times.
✓ Branch 89 → 90 taken 15038 times.
✗ Branch 89 → 93 not taken.
✓ Branch 90 → 91 taken 15029 times.
✓ Branch 90 → 93 taken 9 times.
✓ Branch 91 → 92 taken 15029 times.
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 94 taken 15029 times.
✓ Branch 95 → 96 taken 9 times.
✓ Branch 95 → 100 taken 15047 times.
15056 if (!codeBlock && (source.boolProperty(CodeProperty) || codeMarker || source.fontFixedPitch())) {
90
1/1
✓ Branch 97 → 98 taken 9 times.
9 clean.setProperty(CodeProperty, true);
91
1/1
✓ Branch 99 → 122 taken 9 times.
9 clean.setFontFixedPitch(true);
92
2/2
✓ Branch 100 → 101 taken 15029 times.
✓ Branch 100 → 122 taken 18 times.
15047 } else if (!codeBlock) {
93
4/4
✓ Branch 101 → 102 taken 15029 times.
✓ Branch 102 → 103 taken 15029 times.
✓ Branch 105 → 106 taken 15029 times.
✓ Branch 106 → 107 taken 15029 times.
15029 const QString family = mapForeignFontFamily(source.fontFamilies().toStringList().join(QLatin1Char(',')));
94
5/7
✓ Branch 111 → 112 taken 34 times.
✓ Branch 111 → 120 taken 14995 times.
✓ Branch 114 → 115 taken 34 times.
✓ Branch 117 → 118 taken 34 times.
✓ Branch 117 → 119 taken 34 times.
✗ Branch 245 → 246 not taken.
✗ Branch 245 → 247 not taken.
15097 if (!family.isEmpty()) clean.setFontFamilies({family});
95 15029 }
96
9/10
✓ Branch 122 → 123 taken 14991 times.
✓ Branch 122 → 128 taken 65 times.
✓ Branch 123 → 124 taken 14991 times.
✓ Branch 124 → 125 taken 36 times.
✓ Branch 124 → 128 taken 14955 times.
✓ Branch 125 → 126 taken 36 times.
✓ Branch 126 → 127 taken 36 times.
✗ Branch 126 → 128 not taken.
✓ Branch 129 → 130 taken 36 times.
✓ Branch 129 → 135 taken 15020 times.
15056 if (!heading && source.hasProperty(QTextFormat::FontPointSize) && source.fontPointSize() > 0)
97
2/2
✓ Branch 130 → 131 taken 36 times.
✓ Branch 133 → 134 taken 36 times.
36 clean.setFontPointSize(qBound(FontPointSizeMin, qRound(source.fontPointSize()), FontPointSizeMax));
98
5/10
✓ Branch 135 → 136 taken 14955 times.
✓ Branch 135 → 141 taken 65 times.
✓ Branch 136 → 137 taken 14955 times.
✗ Branch 137 → 138 not taken.
✓ Branch 137 → 141 taken 14955 times.
✗ Branch 138 → 139 not taken.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
✗ Branch 142 → 143 not taken.
✓ Branch 142 → 148 taken 15020 times.
15020 else if (!heading && source.hasProperty(QTextFormat::FontPixelSize) && source.intProperty(QTextFormat::FontPixelSize) > 0)
99 ✗ clean.setFontPointSize(qBound(FontPointSizeMin, qRound(source.intProperty(QTextFormat::FontPixelSize) * 0.75),
100 FontPointSizeMax));
101
9/12
✓ Branch 148 → 149 taken 12841 times.
✓ Branch 148 → 159 taken 2215 times.
✓ Branch 149 → 150 taken 12841 times.
✓ Branch 150 → 151 taken 36 times.
✓ Branch 150 → 159 taken 12805 times.
✓ Branch 151 → 152 taken 36 times.
✓ Branch 153 → 154 taken 36 times.
✗ Branch 153 → 159 not taken.
✓ Branch 164 → 165 taken 24 times.
✓ Branch 164 → 172 taken 15032 times.
✗ Branch 264 → 265 not taken.
✗ Branch 264 → 266 not taken.
30148 if (!anchor && source.hasProperty(QTextFormat::ForegroundBrush) && source.foreground().style() != Qt::NoBrush &&
102
8/10
✓ Branch 154 → 155 taken 36 times.
✓ Branch 156 → 157 taken 36 times.
✓ Branch 157 → 158 taken 24 times.
✓ Branch 157 → 159 taken 12 times.
✓ Branch 160 → 161 taken 36 times.
✓ Branch 160 → 162 taken 15020 times.
✓ Branch 162 → 163 taken 36 times.
✓ Branch 162 → 164 taken 15020 times.
✗ Branch 260 → 261 not taken.
✗ Branch 260 → 262 not taken.
15092 !isDefaultLikeTextColor(source.foreground().color()))
103
3/3
✓ Branch 165 → 166 taken 24 times.
✓ Branch 167 → 168 taken 24 times.
✓ Branch 168 → 169 taken 24 times.
24 clean.setForeground(source.foreground().color());
104
7/10
✓ Branch 172 → 173 taken 15056 times.
✓ Branch 173 → 174 taken 13 times.
✓ Branch 173 → 182 taken 15043 times.
✓ Branch 174 → 175 taken 13 times.
✓ Branch 176 → 177 taken 13 times.
✗ Branch 176 → 182 not taken.
✓ Branch 187 → 188 taken 9 times.
✓ Branch 187 → 195 taken 15047 times.
✗ Branch 278 → 279 not taken.
✗ Branch 278 → 280 not taken.
30125 if (source.hasProperty(QTextFormat::BackgroundBrush) && source.background().style() != Qt::NoBrush &&
105
8/10
✓ Branch 177 → 178 taken 13 times.
✓ Branch 179 → 180 taken 13 times.
✓ Branch 180 → 181 taken 9 times.
✓ Branch 180 → 182 taken 4 times.
✓ Branch 183 → 184 taken 13 times.
✓ Branch 183 → 185 taken 15043 times.
✓ Branch 185 → 186 taken 13 times.
✓ Branch 185 → 187 taken 15043 times.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
15069 !isDefaultLikeBackground(source.background().color()))
106
3/3
✓ Branch 188 → 189 taken 9 times.
✓ Branch 190 → 191 taken 9 times.
✓ Branch 191 → 192 taken 9 times.
9 clean.setBackground(source.background().color());
107
1/1
✓ Branch 195 → 196 taken 15056 times.
15056 return clean;
108 15090 }
109
110 struct BlockPlan {
111 int position = 0;
112 bool blankBefore = false;
113 bool blankAfter = false;
114 };
115
116 3864 QTextBlockFormat cleanBlockFormat(const QTextBlock &block, BlockPlan &plan) {
117 3864 QTextBlockFormat format = block.blockFormat();
118
1/1
✓ Branch 3 → 4 taken 3864 times.
3864 const bool inList = block.textList() != nullptr;
119
1/1
✓ Branch 4 → 5 taken 3864 times.
3864 const int userState = block.userState();
120
5/5
✓ Branch 5 → 6 taken 3847 times.
✓ Branch 5 → 7 taken 17 times.
✓ Branch 6 → 7 taken 10 times.
✓ Branch 6 → 11 taken 3837 times.
✓ Branch 8 → 9 taken 27 times.
3864 if (userState == SignatureRegion || userState == QuoteRegion) format.setProperty(RegionProperty, userState);
121
2/2
✓ Branch 11 → 12 taken 3864 times.
✓ Branch 13 → 14 taken 3864 times.
3864 format.setHeadingLevel(remappedHeading(format.headingLevel()));
122
4/4
✓ Branch 14 → 15 taken 3864 times.
✓ Branch 15 → 16 taken 18 times.
✓ Branch 15 → 21 taken 3846 times.
✓ Branch 18 → 19 taken 18 times.
3864 if (format.nonBreakableLines()) format.setProperty(QTextFormat::BlockCodeFence, QVariant(QChar(u'`')));
123
1/1
✓ Branch 21 → 22 taken 3864 times.
3864 int quote = format.intProperty(QTextFormat::BlockQuoteLevel);
124
9/10
✓ Branch 22 → 23 taken 3840 times.
✓ Branch 22 → 28 taken 24 times.
✓ Branch 23 → 24 taken 3840 times.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 28 taken 3838 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 34 taken 3862 times.
3864 if (quote == 0 && format.leftMargin() >= 30 && format.rightMargin() >= 30)
125
1/1
✓ Branch 30 → 31 taken 2 times.
2 quote = qBound(1, qRound(format.rightMargin() / IndentStep), 8); // Qt's blockquote margins
126
3/3
✓ Branch 34 → 35 taken 26 times.
✓ Branch 34 → 39 taken 3838 times.
✓ Branch 36 → 37 taken 26 times.
3864 if (quote > 0) format.setProperty(QTextFormat::BlockQuoteLevel, quote);
127
5/7
✓ Branch 39 → 40 taken 2924 times.
✓ Branch 39 → 43 taken 914 times.
✓ Branch 40 → 41 taken 2924 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 2924 times.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 50 taken 3838 times.
3838 else if (!inList && format.leftMargin() > 0)
128 ✗ format.setIndent(qBound(0, qRound(format.leftMargin() / IndentStep), 8));
129
5/10
✓ Branch 50 → 51 taken 2924 times.
✓ Branch 50 → 56 taken 914 times.
✓ Branch 51 → 52 taken 2924 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 56 taken 2924 times.
✗ Branch 53 → 54 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 63 taken 3838 times.
3838 else if (!inList && format.rightMargin() > 0 && block.textDirection() == Qt::RightToLeft)
130 ✗ format.setIndent(qBound(0, qRound(format.rightMargin() / IndentStep), 8));
131
10/10
✓ Branch 63 → 64 taken 2950 times.
✓ Branch 63 → 73 taken 914 times.
✓ Branch 64 → 65 taken 2924 times.
✓ Branch 64 → 73 taken 26 times.
✓ Branch 65 → 66 taken 2924 times.
✓ Branch 66 → 67 taken 2859 times.
✓ Branch 66 → 73 taken 65 times.
✓ Branch 67 → 68 taken 2859 times.
✓ Branch 68 → 69 taken 2841 times.
✓ Branch 68 → 73 taken 18 times.
6705 if (!inList && quote == 0 && format.headingLevel() == 0 && !format.hasProperty(QTextFormat::BlockCodeFence) &&
132
7/9
✓ Branch 69 → 70 taken 2841 times.
✓ Branch 71 → 72 taken 2677 times.
✓ Branch 71 → 73 taken 164 times.
✓ Branch 74 → 75 taken 2841 times.
✓ Branch 74 → 76 taken 1023 times.
✓ Branch 76 → 77 taken 2677 times.
✓ Branch 76 → 80 taken 1187 times.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
6705 !block.text().isEmpty()) {
133
1/1
✓ Branch 77 → 78 taken 2677 times.
2677 plan.blankBefore = format.topMargin() >= ParagraphGap;
134
1/1
✓ Branch 78 → 79 taken 2677 times.
2677 plan.blankAfter = format.bottomMargin() >= ParagraphGap;
135 }
136
3/3
✓ Branch 80 → 81 taken 3864 times.
✓ Branch 81 → 82 taken 322 times.
✓ Branch 81 → 98 taken 3542 times.
3864 if (format.hasProperty(QTextFormat::BlockAlignment)) {
137
1/1
✓ Branch 82 → 83 taken 322 times.
322 const Qt::Alignment alignment = format.alignment() & Qt::AlignHorizontal_Mask;
138
6/8
✓ Branch 85 → 86 taken 97 times.
✓ Branch 85 → 91 taken 225 times.
✓ Branch 88 → 89 taken 97 times.
✗ Branch 88 → 91 not taken.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 97 times.
✓ Branch 93 → 94 taken 225 times.
✓ Branch 93 → 95 taken 97 times.
419 if (alignment == Qt::AlignLeft || alignment == (Qt::AlignLeft | Qt::AlignAbsolute) ||
139 97 alignment == Qt::AlignLeading)
140
1/1
✓ Branch 94 → 97 taken 225 times.
225 format.clearProperty(QTextFormat::BlockAlignment);
141 else
142
1/1
✓ Branch 96 → 97 taken 97 times.
97 format.setAlignment(alignment & ~Qt::AlignAbsolute);
143 }
144 50232 for (const int property : {int(QTextFormat::BlockTopMargin), int(QTextFormat::BlockBottomMargin),
145 int(QTextFormat::BlockLeftMargin), int(QTextFormat::BlockRightMargin),
146 int(QTextFormat::TextIndent), int(QTextFormat::LineHeight),
147 int(QTextFormat::LineHeightType), int(QTextFormat::BackgroundBrush),
148 int(QTextFormat::BlockNonBreakableLines), int(QTextFormat::PageBreakPolicy),
149
2/2
✓ Branch 103 → 101 taken 46368 times.
✓ Branch 103 → 104 taken 3864 times.
54096 int(QTextFormat::TabPositions), int(QTextFormat::BlockMarker)})
150
1/1
✓ Branch 101 → 102 taken 46368 times.
46368 format.clearProperty(property);
151
3/3
✓ Branch 104 → 105 taken 914 times.
✓ Branch 104 → 106 taken 2950 times.
✓ Branch 105 → 106 taken 914 times.
3864 if (inList) format.clearProperty(QTextFormat::BlockIndent);
152 3864 return format;
153 ✗ }
154
155 205 void normalizeLists(QTextDocument &document) {
156 205 QSet<QTextList *> seen;
157
5/5
✓ Branch 3 → 4 taken 205 times.
✓ Branch 27 → 28 taken 3864 times.
✓ Branch 30 → 31 taken 4069 times.
✓ Branch 31 → 5 taken 3864 times.
✓ Branch 31 → 32 taken 205 times.
4069 for (QTextBlock block = document.begin(); block.isValid(); block = block.next()) {
158
1/1
✓ Branch 5 → 6 taken 3864 times.
3864 QTextList *list = block.textList();
159
6/6
✓ Branch 6 → 7 taken 914 times.
✓ Branch 6 → 9 taken 2950 times.
✓ Branch 8 → 9 taken 816 times.
✓ Branch 8 → 10 taken 98 times.
✓ Branch 11 → 12 taken 3766 times.
✓ Branch 11 → 13 taken 98 times.
3864 if (!list || seen.contains(list)) continue;
160
1/1
✓ Branch 13 → 14 taken 98 times.
98 seen.insert(list);
161
1/1
✓ Branch 14 → 15 taken 98 times.
98 QTextListFormat format = list->format();
162
1/1
✓ Branch 15 → 16 taken 98 times.
98 QTextListFormat clean;
163
2/2
✓ Branch 16 → 17 taken 98 times.
✓ Branch 18 → 19 taken 98 times.
98 clean.setStyle(allowedListStyle(format.style()));
164
2/2
✓ Branch 19 → 20 taken 98 times.
✓ Branch 21 → 22 taken 98 times.
98 clean.setIndent(qBound(1, format.indent(), MaxListLevel));
165
1/1
✓ Branch 22 → 23 taken 98 times.
98 list->setFormat(clean);
166 98 }
167 205 }
168
169 // The reader flattens tables beyond 32 columns or 128 rows into "a | b"
170 // lines (NativeMailDocument::tableFits); Qt's importer can still build one
171 // from malformed HTML (a table inside <pre>). The composer holds the same
172 // lines, so its own output imports back to the same text
173 // (fuzz_compose_html_import).
174 constexpr int MaxTableColumns = 32;
175 constexpr int MaxTableRows = 128;
176
177 205 void flattenOversizedTables(QTextDocument &document) {
178
1/2
✓ Branch 105 → 3 taken 205 times.
✗ Branch 105 → 106 not taken.
205 for (int round = 0; round < 64; ++round) {
179 205 QTextTable *oversized = nullptr;
180
2/2
✓ Branch 3 → 4 taken 205 times.
✓ Branch 4 → 5 taken 205 times.
205 QList<QTextFrame *> frames = document.rootFrame()->childFrames();
181
5/6
✓ Branch 21 → 22 taken 71 times.
✓ Branch 21 → 24 taken 205 times.
✓ Branch 22 → 23 taken 71 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 6 taken 71 times.
✓ Branch 25 → 26 taken 205 times.
276 while (!frames.isEmpty() && !oversized) {
182
1/1
✓ Branch 6 → 7 taken 71 times.
71 QTextFrame *frame = frames.takeFirst();
183
2/2
✓ Branch 7 → 8 taken 71 times.
✓ Branch 8 → 9 taken 71 times.
71 frames.append(frame->childFrames());
184
1/1
✓ Branch 10 → 11 taken 71 times.
71 auto *table = qobject_cast<QTextTable *>(frame);
185
7/10
✓ Branch 11 → 12 taken 60 times.
✓ Branch 11 → 17 taken 11 times.
✓ Branch 12 → 13 taken 60 times.
✓ Branch 13 → 14 taken 60 times.
✗ Branch 13 → 16 not taken.
✓ Branch 14 → 15 taken 60 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 60 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 71 times.
71 if (table && (table->columns() > MaxTableColumns || table->rows() > MaxTableRows)) oversized = table;
186 }
187
1/2
✓ Branch 26 → 27 taken 205 times.
✗ Branch 26 → 28 not taken.
205 if (!oversized) return;
188 ✗ QStringList lines;
189 ✗ for (int row = 0; row < oversized->rows(); ++row) {
190 ✗ QStringList cells;
191 ✗ for (int column = 0; column < oversized->columns(); ++column) {
192 ✗ const QTextTableCell cell = oversized->cellAt(row, column);
193 ✗ if (!cell.isValid() || cell.row() != row || cell.column() != column) continue;
194 ✗ QStringList parts;
195 ✗ for (QTextBlock block = document.findBlock(cell.firstPosition());
196 ✗ block.isValid() && block.position() <= cell.lastPosition(); block = block.next())
197 ✗ parts.append(block.text());
198 ✗ cells.append(parts.join(QLatin1Char(' ')));
199 ✗ }
200 ✗ lines.append(cells.join(QStringLiteral(" | ")));
201 ✗ }
202 ✗ QTextCursor cursor(&document);
203 ✗ cursor.setPosition(oversized->firstPosition() - 1);
204 ✗ cursor.setPosition(oversized->lastPosition() + 1, QTextCursor::KeepAnchor);
205 ✗ cursor.removeSelectedText();
206 ✗ cursor.insertText(lines.join(QChar(QChar::ParagraphSeparator)), QTextCharFormat());
207 205 }
208 }
209
210 205 void normalizeTables(QTextDocument &document) {
211
1/1
✓ Branch 2 → 3 taken 205 times.
205 flattenOversizedTables(document);
212
2/2
✓ Branch 3 → 4 taken 205 times.
✓ Branch 4 → 5 taken 205 times.
205 QList<QTextFrame *> frames = document.rootFrame()->childFrames();
213
2/2
✓ Branch 52 → 6 taken 71 times.
✓ Branch 52 → 53 taken 205 times.
276 while (!frames.isEmpty()) {
214
1/1
✓ Branch 6 → 7 taken 71 times.
71 QTextFrame *frame = frames.takeFirst();
215
2/2
✓ Branch 7 → 8 taken 71 times.
✓ Branch 8 → 9 taken 71 times.
71 frames.append(frame->childFrames());
216
1/1
✓ Branch 10 → 11 taken 71 times.
71 auto *table = qobject_cast<QTextTable *>(frame);
217
2/2
✓ Branch 11 → 12 taken 11 times.
✓ Branch 11 → 13 taken 60 times.
71 if (!table) continue;
218
1/1
✓ Branch 13 → 14 taken 60 times.
60 const QTextTableFormat source = table->format();
219
1/1
✓ Branch 14 → 15 taken 60 times.
60 QTextTableFormat clean;
220
3/3
✓ Branch 15 → 16 taken 60 times.
✓ Branch 16 → 17 taken 60 times.
✓ Branch 18 → 19 taken 60 times.
60 clean.setHeaderRowCount(qBound(0, source.headerRowCount(), table->rows()));
221
3/4
✓ Branch 19 → 20 taken 60 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 60 times.
✓ Branch 23 → 24 taken 60 times.
60 clean.setBorder(source.border() > 0 ? 1 : 0);
222
1/1
✓ Branch 24 → 25 taken 60 times.
60 clean.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
223
1/1
✓ Branch 25 → 26 taken 60 times.
60 clean.setBorderCollapse(true);
224
1/1
✓ Branch 26 → 27 taken 60 times.
60 clean.setCellPadding(4);
225
1/1
✓ Branch 27 → 28 taken 60 times.
60 clean.setCellSpacing(0);
226 60 QList<QTextLength> widths;
227
5/5
✓ Branch 28 → 29 taken 60 times.
✓ Branch 29 → 30 taken 60 times.
✓ Branch 30 → 31 taken 60 times.
✓ Branch 40 → 32 taken 113 times.
✓ Branch 40 → 41 taken 60 times.
173 for (const QTextLength &width : source.columnWidthConstraints())
228
2/3
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 113 times.
✓ Branch 37 → 38 taken 113 times.
173 widths.append(width.type() == QTextLength::PercentageLength ? width : QTextLength());
229
2/3
✓ Branch 43 → 44 taken 60 times.
✗ Branch 43 → 45 not taken.
✓ Branch 44 → 45 taken 60 times.
60 if (!widths.isEmpty()) clean.setColumnWidthConstraints(widths);
230
1/1
✓ Branch 45 → 46 taken 60 times.
60 table->setFormat(clean);
231 60 }
232 205 }
233
234 // Splits blocks at Shift+Enter line separators (written as <br>) so own
235 // HTML comes back as the blocks it was written from. A <br> that ends its
236 // block adds no line in a browser (<div><br></div> is one empty line), so
237 // a trailing separator is only removed.
238 205 void splitLineSeparators(QTextDocument &document) {
239
1/1
✓ Branch 2 → 3 taken 205 times.
205 QTextCursor cursor(&document);
240 while (true) {
241
2/2
✓ Branch 5 → 6 taken 512 times.
✓ Branch 6 → 7 taken 512 times.
512 cursor = document.find(QString(QChar(0x2028)), cursor);
242
3/3
✓ Branch 10 → 11 taken 512 times.
✓ Branch 11 → 12 taken 205 times.
✓ Branch 11 → 14 taken 307 times.
512 if (cursor.isNull()) break;
243
5/5
✓ Branch 14 → 15 taken 307 times.
✓ Branch 15 → 16 taken 307 times.
✓ Branch 16 → 17 taken 307 times.
✓ Branch 17 → 18 taken 307 times.
✓ Branch 18 → 19 taken 307 times.
307 const bool trailing = cursor.selectionEnd() == cursor.block().position() + cursor.block().length() - 1;
244
1/1
✓ Branch 19 → 20 taken 307 times.
307 const QTextBlockFormat blockFormat = cursor.blockFormat();
245
1/1
✓ Branch 20 → 21 taken 307 times.
307 const QTextCharFormat charFormat = cursor.charFormat();
246
2/2
✓ Branch 21 → 22 taken 307 times.
✓ Branch 22 → 23 taken 307 times.
307 const int userState = cursor.block().userState();
247
1/1
✓ Branch 23 → 24 taken 307 times.
307 cursor.removeSelectedText();
248
2/2
✓ Branch 24 → 25 taken 207 times.
✓ Branch 24 → 29 taken 100 times.
307 if (!trailing) {
249
1/1
✓ Branch 25 → 26 taken 207 times.
207 cursor.insertBlock(blockFormat, charFormat);
250
2/2
✓ Branch 26 → 27 taken 207 times.
✓ Branch 27 → 28 taken 207 times.
207 cursor.block().setUserState(userState); // the region marker follows the line
251 }
252 307 }
253 205 }
254
255 // Blocks of spaces and non-breaking spaces only (Outlook's empty lines) are
256 // empty lines; spaces that end the document are HTML whitespace.
257 205 void clearBlankBlocks(QTextDocument &document) {
258
5/5
✓ Branch 2 → 3 taken 205 times.
✓ Branch 42 → 43 taken 3864 times.
✓ Branch 45 → 46 taken 4069 times.
✓ Branch 46 → 4 taken 3864 times.
✓ Branch 46 → 47 taken 205 times.
4069 for (QTextBlock block = document.begin(); block.isValid(); block = block.next()) {
259
1/1
✓ Branch 4 → 5 taken 3864 times.
3864 const QString text = block.text();
260
7/7
✓ Branch 6 → 7 taken 3704 times.
✓ Branch 6 → 9 taken 160 times.
✓ Branch 7 → 8 taken 3704 times.
✓ Branch 8 → 9 taken 914 times.
✓ Branch 8 → 10 taken 2790 times.
✓ Branch 11 → 12 taken 1074 times.
✓ Branch 11 → 13 taken 2790 times.
3864 if (text.isEmpty() || block.textList()) continue;
261 2790 bool blank = true;
262
7/8
✓ Branch 18 → 19 taken 223365 times.
✓ Branch 18 → 26 taken 61595 times.
✓ Branch 21 → 22 taken 223361 times.
✓ Branch 21 → 26 taken 4 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 223361 times.
✓ Branch 29 → 15 taken 284960 times.
✓ Branch 29 → 30 taken 2790 times.
287750 for (const QChar c : text) blank &= c == QLatin1Char(' ') || c == QChar(0x00A0) || c == QLatin1Char('\t');
263
2/2
✓ Branch 30 → 31 taken 2786 times.
✓ Branch 30 → 32 taken 4 times.
2790 if (!blank) continue;
264
1/1
✓ Branch 32 → 33 taken 4 times.
4 QTextCursor cursor(block);
265
1/1
✓ Branch 33 → 34 taken 4 times.
4 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
266
1/1
✓ Branch 34 → 35 taken 4 times.
4 cursor.removeSelectedText();
267 3864 }
268
1/1
✓ Branch 47 → 48 taken 205 times.
205 QTextBlock last = document.lastBlock();
269
1/1
✓ Branch 48 → 49 taken 205 times.
205 const QString text = last.text();
270 205 qsizetype keep = text.size();
271
6/6
✓ Branch 52 → 53 taken 282 times.
✓ Branch 52 → 59 taken 13 times.
✓ Branch 57 → 58 taken 90 times.
✓ Branch 57 → 59 taken 192 times.
✓ Branch 60 → 51 taken 90 times.
✓ Branch 60 → 61 taken 205 times.
295 while (keep > 0 && text.at(keep - 1) == QLatin1Char(' ')) --keep;
272
2/2
✓ Branch 62 → 63 taken 90 times.
✓ Branch 62 → 70 taken 115 times.
205 if (keep < text.size()) {
273
1/1
✓ Branch 63 → 64 taken 90 times.
90 QTextCursor cursor(last);
274
2/2
✓ Branch 64 → 65 taken 90 times.
✓ Branch 65 → 66 taken 90 times.
90 cursor.setPosition(last.position() + int(keep));
275
1/1
✓ Branch 66 → 67 taken 90 times.
90 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
276
1/1
✓ Branch 67 → 68 taken 90 times.
90 cursor.removeSelectedText();
277 90 }
278 205 }
279
280 48 bool inTable(const QTextBlock &block) {
281
2/2
✓ Branch 2 → 3 taken 48 times.
✓ Branch 3 → 4 taken 48 times.
48 return QTextCursor(block).currentTable() != nullptr;
282 }
283
284 // Empty lines at the very start and end are HTML whitespace. A deletion
285 // that changes nothing (a block inside a table) ends the loop.
286 205 void trimEmptyEdges(QTextDocument &document) {
287
8/9
✓ Branch 32 → 33 taken 205 times.
✓ Branch 34 → 35 taken 149 times.
✓ Branch 35 → 36 taken 149 times.
✓ Branch 36 → 37 taken 14 times.
✓ Branch 36 → 51 taken 135 times.
✓ Branch 37 → 38 taken 14 times.
✓ Branch 38 → 39 taken 14 times.
✓ Branch 39 → 40 taken 14 times.
✗ Branch 39 → 51 not taken.
354 while (document.blockCount() > 1 && document.firstBlock().length() <= 1 && !document.firstBlock().textList() &&
288
8/10
✓ Branch 33 → 34 taken 149 times.
✓ Branch 33 → 51 taken 56 times.
✓ Branch 40 → 41 taken 14 times.
✓ Branch 41 → 42 taken 14 times.
✓ Branch 42 → 43 taken 14 times.
✗ Branch 42 → 51 not taken.
✓ Branch 43 → 44 taken 14 times.
✓ Branch 44 → 45 taken 14 times.
✓ Branch 45 → 46 taken 14 times.
✗ Branch 45 → 51 not taken.
368 !isHorizontalRule(document.firstBlock()) && !inTable(document.firstBlock()) &&
289
5/7
✓ Branch 46 → 47 taken 14 times.
✓ Branch 47 → 48 taken 14 times.
✓ Branch 48 → 49 taken 14 times.
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 14 times.
✗ Branch 52 → 3 not taken.
✓ Branch 52 → 53 taken 205 times.
219 !inTable(document.firstBlock().next())) {
290 // Merging keeps the first block's format: carry the second one's over.
291 ✗ const QTextBlockFormat second = document.firstBlock().next().blockFormat();
292 ✗ const QTextCharFormat secondChars = document.firstBlock().next().charFormat();
293 ✗ const int before = document.blockCount();
294 ✗ QTextCursor cursor(document.firstBlock());
295 ✗ cursor.deleteChar();
296 ✗ if (document.blockCount() == before) break;
297 ✗ cursor.setBlockFormat(second);
298 ✗ cursor.setBlockCharFormat(secondChars);
299 ✗ }
300
8/9
✓ Branch 67 → 68 taken 206 times.
✓ Branch 69 → 70 taken 150 times.
✓ Branch 70 → 71 taken 150 times.
✓ Branch 71 → 72 taken 10 times.
✓ Branch 71 → 86 taken 140 times.
✓ Branch 72 → 73 taken 10 times.
✓ Branch 73 → 74 taken 10 times.
✓ Branch 74 → 75 taken 10 times.
✗ Branch 74 → 86 not taken.
356 while (document.blockCount() > 1 && document.lastBlock().length() <= 1 && !document.lastBlock().textList() &&
301
8/10
✓ Branch 68 → 69 taken 150 times.
✓ Branch 68 → 86 taken 56 times.
✓ Branch 75 → 76 taken 10 times.
✓ Branch 76 → 77 taken 10 times.
✓ Branch 77 → 78 taken 10 times.
✗ Branch 77 → 86 not taken.
✓ Branch 78 → 79 taken 10 times.
✓ Branch 79 → 80 taken 10 times.
✓ Branch 80 → 81 taken 10 times.
✗ Branch 80 → 86 not taken.
366 !isHorizontalRule(document.lastBlock()) && !inTable(document.lastBlock()) &&
302
7/7
✓ Branch 81 → 82 taken 10 times.
✓ Branch 82 → 83 taken 10 times.
✓ Branch 83 → 84 taken 10 times.
✓ Branch 84 → 85 taken 1 time.
✓ Branch 84 → 86 taken 9 times.
✓ Branch 87 → 54 taken 1 time.
✓ Branch 87 → 88 taken 205 times.
216 !inTable(document.lastBlock().previous())) {
303
1/1
✓ Branch 54 → 55 taken 1 time.
1 const int before = document.blockCount();
304
2/2
✓ Branch 55 → 56 taken 1 time.
✓ Branch 56 → 57 taken 1 time.
1 QTextCursor cursor(document.lastBlock());
305
1/1
✓ Branch 57 → 58 taken 1 time.
1 cursor.deletePreviousChar();
306
2/3
✓ Branch 58 → 59 taken 1 time.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 1 time.
1 if (document.blockCount() == before) break;
307 1 }
308 205 }
309
310 } // namespace
311
312 205 void ComposeHtmlImport::normalize(QTextDocument &document, const QHash<QString, QString> &alternativeTexts) {
313
1/1
✓ Branch 2 → 3 taken 205 times.
205 splitLineSeparators(document);
314
1/1
✓ Branch 3 → 4 taken 205 times.
205 clearBlankBlocks(document);
315
1/1
✓ Branch 4 → 5 taken 205 times.
205 normalizeTables(document);
316
1/1
✓ Branch 5 → 6 taken 205 times.
205 normalizeLists(document);
317 // Formats first (positions stay), then blank lines from paragraph margins
318 // (from the end, so earlier positions stay valid).
319 205 QVector<BlockPlan> plans;
320
1/1
✓ Branch 6 → 7 taken 205 times.
205 QTextCursor cursor(&document);
321
1/1
✓ Branch 7 → 8 taken 205 times.
205 cursor.beginEditBlock();
322
5/5
✓ Branch 8 → 9 taken 205 times.
✓ Branch 53 → 54 taken 3864 times.
✓ Branch 56 → 57 taken 4069 times.
✓ Branch 57 → 10 taken 3864 times.
✓ Branch 57 → 58 taken 205 times.
4069 for (QTextBlock block = document.begin(); block.isValid(); block = block.next()) {
323 3864 BlockPlan plan;
324
1/1
✓ Branch 10 → 11 taken 3864 times.
3864 plan.position = block.position();
325
1/1
✓ Branch 11 → 12 taken 3864 times.
3864 const QTextBlockFormat blockFormat = cleanBlockFormat(block, plan);
326
1/1
✓ Branch 12 → 13 taken 3864 times.
3864 const bool heading = blockFormat.headingLevel() > 0;
327
1/1
✓ Branch 13 → 14 taken 3864 times.
3864 const bool codeBlock = blockFormat.hasProperty(QTextFormat::BlockCodeFence);
328
1/1
✓ Branch 14 → 15 taken 3864 times.
3864 const bool headerCell = inHeaderRow(block);
329 struct Span { int start, length; QTextCharFormat format; };
330 3864 QVector<Span> spans;
331
4/4
✓ Branch 15 → 16 taken 3864 times.
✓ Branch 28 → 29 taken 15067 times.
✓ Branch 30 → 17 taken 15067 times.
✓ Branch 30 → 31 taken 3864 times.
18931 for (auto it = block.begin(); !it.atEnd(); ++it) {
332
1/1
✓ Branch 17 → 18 taken 15067 times.
15067 const QTextFragment fragment = it.fragment();
333
1/2
✓ Branch 19 → 20 taken 15067 times.
✗ Branch 19 → 28 not taken.
15067 if (fragment.isValid())
334
2/2
✓ Branch 20 → 21 taken 15067 times.
✓ Branch 24 → 25 taken 15067 times.
30134 spans.append({fragment.position(), fragment.length(),
335 15067 cleanCharFormat(fragment.charFormat(), heading, codeBlock, headerCell, alternativeTexts)});
336 }
337
1/1
✓ Branch 31 → 32 taken 3864 times.
3864 QTextCursor blockCursor(block);
338
1/1
✓ Branch 32 → 33 taken 3864 times.
3864 blockCursor.setBlockFormat(blockFormat);
339
2/2
✓ Branch 33 → 34 taken 3864 times.
✓ Branch 34 → 35 taken 3864 times.
3864 blockCursor.setBlockCharFormat(QTextCharFormat());
340
4/4
✓ Branch 36 → 37 taken 3864 times.
✓ Branch 37 → 38 taken 3864 times.
✓ Branch 47 → 39 taken 15067 times.
✓ Branch 47 → 48 taken 3864 times.
18931 for (const Span &span : spans) {
341
1/1
✓ Branch 40 → 41 taken 15067 times.
15067 QTextCursor spanCursor(&document);
342
1/1
✓ Branch 41 → 42 taken 15067 times.
15067 spanCursor.setPosition(span.start);
343
1/1
✓ Branch 42 → 43 taken 15067 times.
15067 spanCursor.setPosition(span.start + span.length, QTextCursor::KeepAnchor);
344
1/1
✓ Branch 43 → 44 taken 15067 times.
15067 spanCursor.setCharFormat(span.format);
345 15067 }
346
1/1
✓ Branch 48 → 49 taken 3864 times.
3864 block.setUserState(-1);
347
1/1
✓ Branch 49 → 50 taken 3864 times.
3864 plans.append(plan);
348 3864 }
349
2/2
✓ Branch 127 → 60 taken 3864 times.
✓ Branch 127 → 128 taken 205 times.
4069 for (qsizetype i = plans.size() - 1; i >= 0; --i) {
350 3864 const BlockPlan &plan = plans.at(i);
351
1/1
✓ Branch 61 → 62 taken 3864 times.
3864 QTextBlock block = document.findBlock(plan.position);
352
2/2
✓ Branch 62 → 63 taken 1920 times.
✓ Branch 62 → 101 taken 1944 times.
3864 if (plan.blankAfter) {
353
1/1
✓ Branch 63 → 64 taken 1920 times.
1920 const QTextBlock next = block.next();
354
8/8
✓ Branch 64 → 65 taken 1920 times.
✓ Branch 65 → 66 taken 1854 times.
✓ Branch 65 → 69 taken 66 times.
✓ Branch 66 → 67 taken 1854 times.
✓ Branch 67 → 68 taken 145 times.
✓ Branch 67 → 69 taken 1709 times.
✓ Branch 70 → 71 taken 145 times.
✓ Branch 70 → 100 taken 1775 times.
1920 if (next.isValid() && next.length() > 1) {
355
1/1
✓ Branch 71 → 72 taken 145 times.
145 QTextCursor at(block);
356
1/1
✓ Branch 72 → 73 taken 145 times.
145 at.movePosition(QTextCursor::EndOfBlock);
357
1/1
✓ Branch 73 → 74 taken 145 times.
145 QTextBlockFormat plain;
358
4/4
✓ Branch 74 → 75 taken 145 times.
✓ Branch 75 → 76 taken 145 times.
✓ Branch 77 → 78 taken 6 times.
✓ Branch 77 → 84 taken 139 times.
145 if (block.blockFormat().hasProperty(RegionProperty))
359
3/3
✓ Branch 78 → 79 taken 6 times.
✓ Branch 79 → 80 taken 6 times.
✓ Branch 80 → 81 taken 6 times.
6 plain.setProperty(RegionProperty, block.blockFormat().property(RegionProperty));
360
3/4
✓ Branch 84 → 85 taken 145 times.
✓ Branch 85 → 86 taken 145 times.
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 94 taken 145 times.
145 if (block.blockFormat().hasProperty(QTextFormat::BlockQuoteLevel))
361 ✗ plain.setProperty(QTextFormat::BlockQuoteLevel, block.blockFormat().property(QTextFormat::BlockQuoteLevel));
362
2/2
✓ Branch 94 → 95 taken 145 times.
✓ Branch 95 → 96 taken 145 times.
145 at.insertBlock(plain, QTextCharFormat());
363 145 }
364 }
365
2/2
✓ Branch 101 → 102 taken 1912 times.
✓ Branch 101 → 126 taken 1952 times.
3864 if (plan.blankBefore) {
366
1/1
✓ Branch 102 → 103 taken 1912 times.
1912 const QTextBlock previous = block.previous();
367
8/8
✓ Branch 103 → 104 taken 1912 times.
✓ Branch 104 → 105 taken 1854 times.
✓ Branch 104 → 108 taken 58 times.
✓ Branch 105 → 106 taken 1854 times.
✓ Branch 106 → 107 taken 1807 times.
✓ Branch 106 → 108 taken 47 times.
✓ Branch 109 → 110 taken 1807 times.
✓ Branch 109 → 125 taken 105 times.
1912 if (previous.isValid() && previous.length() > 1) {
368
1/1
✓ Branch 110 → 111 taken 1807 times.
1807 QTextCursor at(block);
369
1/1
✓ Branch 111 → 112 taken 1807 times.
1807 at.movePosition(QTextCursor::StartOfBlock);
370
3/3
✓ Branch 112 → 113 taken 1807 times.
✓ Branch 113 → 114 taken 1807 times.
✓ Branch 114 → 115 taken 1807 times.
1807 at.insertBlock(block.blockFormat(), QTextCharFormat());
371
2/2
✓ Branch 117 → 118 taken 1807 times.
✓ Branch 118 → 119 taken 1807 times.
1807 QTextCursor empty(document.findBlock(plan.position));
372
1/1
✓ Branch 119 → 120 taken 1807 times.
1807 QTextBlockFormat plain;
373
1/1
✓ Branch 120 → 121 taken 1807 times.
1807 empty.setBlockFormat(plain);
374 1807 }
375 }
376 }
377
1/1
✓ Branch 128 → 129 taken 205 times.
205 trimEmptyEdges(document);
378
1/1
✓ Branch 129 → 130 taken 205 times.
205 cursor.endEditBlock();
379 15272 }
380
381 207 ComposeHtmlImport::Result ComposeHtmlImport::import(const QByteArray &html, const Options &options,
382 const std::atomic_bool *cancelled) {
383
1/1
✓ Branch 2 → 3 taken 207 times.
207 Result result;
384
1/1
✓ Branch 3 → 4 taken 207 times.
207 NativeMailDocument prepared = NativeMailDocument::prepare(html, cancelled, NativeMailDocument::Mode::Compose);
385 207 result.outcome = prepared.outcome;
386 207 result.simplified = prepared.simplified;
387
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 205 times.
207 if (prepared.outcome != NativeMailDocument::Outcome::Ready) return result;
388 205 QString body = prepared.html;
389 205 QHash<QString, QString> alternativeTexts;
390
2/2
✓ Branch 122 → 10 taken 148 times.
✓ Branch 122 → 123 taken 205 times.
353 for (const auto &reference : std::as_const(prepared.imageReferences)) {
391 148 QString replacement;
392 148 const QString alt = reference.alternativeText;
393 148 QString size;
394
4/4
✓ Branch 12 → 13 taken 9 times.
✓ Branch 12 → 23 taken 139 times.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 9 times.
166 if (reference.width > 0) size += QStringLiteral(" width=\"%1\"").arg(reference.width);
395
4/4
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 34 taken 146 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
152 if (reference.height > 0) size += QStringLiteral(" height=\"%1\"").arg(reference.height);
396
2/2
✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 64 taken 144 times.
148 if (!reference.data.isEmpty()) {
397
4/4
✓ Branch 36 → 37 taken 4 times.
✓ Branch 37 → 38 taken 4 times.
✓ Branch 41 → 42 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
8 const QString name = ImageScheme + QStringLiteral("pasted-") + QUuid::createUuid().toString(QUuid::Id128);
398 4 result.images.append({name, reference.data, reference.mimeType, alt});
399
1/1
✓ Branch 53 → 54 taken 4 times.
4 alternativeTexts.insert(name, alt);
400
1/1
✓ Branch 57 → 58 taken 4 times.
4 replacement = QStringLiteral("<img src=\"%1\"%2>").arg(name, size);
401
6/6
✓ Branch 65 → 66 taken 12 times.
✓ Branch 65 → 69 taken 132 times.
✓ Branch 67 → 68 taken 7 times.
✓ Branch 67 → 69 taken 5 times.
✓ Branch 70 → 71 taken 7 times.
✓ Branch 70 → 85 taken 137 times.
148 } else if (!reference.contentId.isEmpty() && options.contentIdImages.contains(reference.contentId)) {
402 7 const QString name = options.contentIdImages.value(reference.contentId);
403
1/1
✓ Branch 72 → 73 taken 7 times.
7 alternativeTexts.insert(name, alt);
404
2/2
✓ Branch 76 → 77 taken 7 times.
✓ Branch 77 → 78 taken 7 times.
7 replacement = QStringLiteral("<img src=\"%1\"%2>").arg(name.toHtmlEscaped(), size);
405
3/3
✓ Branch 85 → 86 taken 137 times.
✓ Branch 86 → 87 taken 132 times.
✓ Branch 86 → 112 taken 5 times.
144 } else if (!reference.remoteUrl.isEmpty()) {
406 132 ++result.remoteImagesLeftOut;
407 264 replacement = QStringLiteral("<a href=\"%1\">%2</a>")
408
3/3
✓ Branch 98 → 99 taken 132 times.
✓ Branch 99 → 100 taken 132 times.
✓ Branch 100 → 101 taken 132 times.
396 .arg(reference.remoteUrl.toString(QUrl::FullyEncoded).toHtmlEscaped(),
409
5/7
✓ Branch 91 → 92 taken 130 times.
✓ Branch 91 → 95 taken 2 times.
✓ Branch 96 → 97 taken 132 times.
✓ Branch 107 → 108 taken 130 times.
✓ Branch 107 → 109 taken 2 times.
✗ Branch 256 → 257 not taken.
✗ Branch 256 → 258 not taken.
526 (alt.isEmpty() ? QStringLiteral("[image]") : alt).toHtmlEscaped());
410 } else {
411
1/1
✓ Branch 112 → 113 taken 5 times.
5 replacement = alt.toHtmlEscaped();
412 }
413
1/1
✓ Branch 116 → 117 taken 148 times.
148 body.replace(reference.placeholder, replacement);
414 148 }
415
4/6
✓ Branch 123 → 124 taken 2 times.
✓ Branch 123 → 127 taken 203 times.
✗ Branch 125 → 126 not taken.
✓ Branch 125 → 127 taken 2 times.
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 130 taken 205 times.
205 if (cancelled && cancelled->load()) {
416 ✗ result.outcome = NativeMailDocument::Outcome::Cancelled;
417 ✗ return result;
418 }
419 // C0/C1 control characters are invisible, not valid HTML text, and the
420 // writer leaves them out; dropping them here keeps an import of the
421 // composer's own output equal to the first one (fuzz_compose_html_import).
422 // (Also those written as character references: removed from the document.)
423
4/7
✓ Branch 130 → 131 taken 9 times.
✓ Branch 130 → 143 taken 196 times.
✓ Branch 132 → 133 taken 9 times.
✗ Branch 132 → 143 not taken.
✓ Branch 137 → 138 taken 9 times.
✗ Branch 285 → 286 not taken.
✗ Branch 285 → 287 not taken.
214 static const QRegularExpression controls(QStringLiteral("[\\x{1}-\\x{8}\\x{B}\\x{C}\\x{E}-\\x{1F}\\x{7F}-\\x{9F}]"));
424
1/1
✓ Branch 144 → 145 taken 205 times.
205 body.remove(QChar(0));
425
1/1
✓ Branch 145 → 146 taken 205 times.
205 body.remove(controls);
426
1/1
✓ Branch 146 → 147 taken 205 times.
205 ResourceFreeDocument document;
427
3/5
✓ Branch 147 → 148 taken 205 times.
✓ Branch 148 → 149 taken 205 times.
✗ Branch 150 → 151 not taken.
✓ Branch 150 → 152 taken 205 times.
✗ Branch 151 → 152 not taken.
205 if (options.defaultFont != QFont()) document.setDefaultFont(options.defaultFont);
428
1/1
✓ Branch 152 → 153 taken 205 times.
205 document.setHtml(body);
429
3/5
✓ Branch 154 → 155 taken 205 times.
✗ Branch 158 → 159 not taken.
✓ Branch 162 → 163 taken 205 times.
✗ Branch 163 → 156 not taken.
✓ Branch 163 → 164 taken 205 times.
205 for (QTextCursor found = document.find(controls); !found.isNull(); found = document.find(controls, found))
430
0/1
✗ Branch 156 → 157 not taken.
205 found.removeSelectedText();
431
1/1
✓ Branch 165 → 166 taken 205 times.
205 normalize(document, alternativeTexts);
432
2/2
✓ Branch 166 → 167 taken 205 times.
✓ Branch 167 → 168 taken 205 times.
205 result.fragment = QTextDocumentFragment(&document);
433 // Qt 6.8 creates the fragment's root frame lazily on first traversal;
434 // materialise it on this thread (as NativeDocumentService does).
435
1/1
✓ Branch 169 → 170 taken 205 times.
205 (void)result.fragment.toHtml();
436 205 return result;
437 211 }
438