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: 94.6% 370 / 0 / 391
Functions: 100.0% 31 / 0 / 31
Branches: 81.2% 648 / 0 / 798

data/MailHtmlWriter.cpp
Line Branch Exec Source
1 #include "MailHtmlWriter.h"
2
3 #include "ComposeDocumentFormat.h"
4
5 #include <QTextBlock>
6 #include <QTextCharFormat>
7 #include <QTextDocument>
8 #include <QTextFrame>
9 #include <QTextImageFormat>
10 #include <QTextList>
11 #include <QTextTable>
12 #include <QVector>
13
14 namespace {
15
16 using namespace ComposeDocumentFormat;
17
18 // A paragraph's attributes; consecutive plain blocks with equal attributes
19 // share one <p> (lines joined by <br>, an empty block ends the paragraph).
20 struct ParagraphStyle {
21 QString align; // "", "left", "right", "center", "justify"
22 bool rtl = false;
23 int indent = 0;
24 585 bool operator==(const ParagraphStyle &other) const {
25
4/6
✓ Branch 3 → 4 taken 582 times.
✓ Branch 3 → 7 taken 3 times.
✓ Branch 4 → 5 taken 582 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 582 times.
✗ Branch 5 → 7 not taken.
585 return align == other.align && rtl == other.rtl && indent == other.indent;
26 }
27 };
28
29 4800 QString physicalAlignment(const QTextBlockFormat &format, bool rtl) {
30
3/3
✓ Branch 2 → 3 taken 4800 times.
✓ Branch 3 → 4 taken 4783 times.
✓ Branch 3 → 5 taken 17 times.
4800 if (!format.hasProperty(QTextFormat::BlockAlignment)) return {};
31
1/1
✓ Branch 5 → 6 taken 17 times.
17 const Qt::Alignment alignment = format.alignment();
32
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 17 times.
17 if (alignment & Qt::AlignJustify) return QStringLiteral("justify");
33
2/2
✓ Branch 16 → 17 taken 9 times.
✓ Branch 16 → 22 taken 8 times.
26 if (alignment & Qt::AlignHCenter) return QStringLiteral("center");
34 8 const bool absolute = alignment & Qt::AlignAbsolute;
35
1/2
✓ Branch 26 → 27 taken 8 times.
✗ Branch 26 → 41 not taken.
8 if (alignment & Qt::AlignRight)
36
4/8
✓ Branch 27 → 28 taken 8 times.
✗ Branch 27 → 29 not taken.
✓ Branch 28 → 29 taken 8 times.
✗ Branch 28 → 32 not taken.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 8 times.
✓ Branch 38 → 39 taken 8 times.
✗ Branch 38 → 40 not taken.
24 return (absolute || !rtl) ? QStringLiteral("right") : QStringLiteral("left");
37 ✗ if (alignment & Qt::AlignLeft) {
38 // Logical start is the default: written only when it differs (absolute
39 // left in a right-to-left paragraph).
40 ✗ if (absolute && rtl) return QStringLiteral("left");
41 ✗ return {};
42 }
43 ✗ return {};
44 }
45
46 4800 ParagraphStyle paragraphStyle(const QTextBlock &block) {
47 4800 ParagraphStyle style;
48
1/1
✓ Branch 2 → 3 taken 4800 times.
4800 style.rtl = block.textDirection() == Qt::RightToLeft;
49
2/2
✓ Branch 3 → 4 taken 4800 times.
✓ Branch 4 → 5 taken 4800 times.
4800 style.align = physicalAlignment(block.blockFormat(), style.rtl);
50
2/2
✓ Branch 8 → 9 taken 4800 times.
✓ Branch 9 → 10 taken 4800 times.
4800 style.indent = qBound(0, block.blockFormat().indent(), 8);
51 4800 return style;
52 ✗ }
53
54 4218 QString blockAttributes(const ParagraphStyle &style, const QString &baseStyle) {
55 4218 QStringList declarations;
56
3/3
✓ Branch 3 → 4 taken 3471 times.
✓ Branch 3 → 5 taken 747 times.
✓ Branch 4 → 5 taken 3471 times.
4218 if (!baseStyle.isEmpty()) declarations.append(baseStyle);
57
4/4
✓ Branch 6 → 7 taken 17 times.
✓ Branch 6 → 16 taken 4201 times.
✓ Branch 10 → 11 taken 17 times.
✓ Branch 11 → 12 taken 17 times.
4235 if (!style.align.isEmpty()) declarations.append(QStringLiteral("text-align:") + style.align);
58
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 42 taken 4218 times.
4218 if (style.indent > 0)
59 ✗ declarations.append((style.rtl ? QStringLiteral("margin-right:") : QStringLiteral("margin-left:")) +
60 ✗ QString::number(style.indent * 40) + QStringLiteral("px"));
61 4218 QString attributes;
62
3/3
✓ Branch 42 → 43 taken 26 times.
✓ Branch 42 → 50 taken 4192 times.
✓ Branch 46 → 47 taken 26 times.
4244 if (style.rtl) attributes += QStringLiteral(" dir=\"rtl\"");
63
6/6
✓ Branch 51 → 52 taken 3471 times.
✓ Branch 51 → 69 taken 747 times.
✓ Branch 56 → 57 taken 3471 times.
✓ Branch 60 → 61 taken 3471 times.
✓ Branch 61 → 62 taken 3471 times.
✓ Branch 62 → 63 taken 3471 times.
7689 if (!declarations.isEmpty()) attributes += QStringLiteral(" style=\"") + declarations.join(QLatin1Char(';')) + QLatin1Char('"');
64 4218 return attributes;
65 4218 }
66
67 // `previousSpace` carries the space run across the fragments of one block.
68 11580 QString escapedText(const QString &text, bool &previousSpace) {
69 11580 QString out;
70
1/1
✓ Branch 3 → 4 taken 11580 times.
11580 out.reserve(text.size() + 16);
71
2/2
✓ Branch 78 → 6 taken 304961 times.
✓ Branch 78 → 79 taken 11580 times.
316541 for (const QChar c : text) {
72
7/10
✓ Branch 7 → 8 taken 33 times.
✓ Branch 7 → 15 taken 39 times.
✓ Branch 7 → 22 taken 17 times.
✓ Branch 7 → 29 taken 4 times.
✗ Branch 7 → 36 not taken.
✗ Branch 7 → 43 not taken.
✓ Branch 7 → 50 taken 2 times.
✗ Branch 7 → 57 not taken.
✓ Branch 7 → 58 taken 71456 times.
✓ Branch 7 → 72 taken 233410 times.
304961 switch (c.unicode()) {
73
1/1
✓ Branch 11 → 12 taken 33 times.
66 case '<': out += QStringLiteral("&lt;"); break;
74
1/1
✓ Branch 18 → 19 taken 39 times.
78 case '>': out += QStringLiteral("&gt;"); break;
75
1/1
✓ Branch 25 → 26 taken 17 times.
34 case '&': out += QStringLiteral("&amp;"); break;
76
1/1
✓ Branch 32 → 33 taken 4 times.
8 case '"': out += QStringLiteral("&quot;"); break;
77 ✗ case 0x2028: out += QStringLiteral("<br>"); break; // Shift+Enter line break
78 ✗ case 0x00A0: out += QStringLiteral("&nbsp;"); break;
79
1/1
✓ Branch 53 → 54 taken 2 times.
4 case '\t': out += QStringLiteral("&nbsp;&nbsp;&nbsp; "); break;
80 ✗ case 0xFFFC: break; // object without image format
81 71456 case ' ':
82 // Runs of spaces keep their width: every second one is non-breaking.
83
4/11
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 62 taken 71456 times.
✓ Branch 65 → 66 taken 71456 times.
✓ Branch 67 → 68 taken 71456 times.
✗ Branch 67 → 69 not taken.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 71456 times.
✗ Branch 125 → 126 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
142912 out += previousSpace ? QStringLiteral("&nbsp;") : QStringLiteral(" ");
84 71456 previousSpace = !previousSpace;
85 71456 continue;
86 233410 default:
87
2/2
✓ Branch 73 → 74 taken 2 times.
✓ Branch 73 → 75 taken 233408 times.
233410 if (c.unicode() < 0x20) break; // control characters never leave
88
1/1
✓ Branch 75 → 76 taken 233408 times.
233408 out += c;
89 }
90 233505 previousSpace = false;
91 }
92 11580 return out;
93 ✗ }
94
95 16 QString escapedText(const QString &text) {
96 16 bool previousSpace = false;
97
1/1
✓ Branch 2 → 3 taken 16 times.
32 return escapedText(text, previousSpace);
98 }
99
100 11564 QString spanStyle(const QTextCharFormat &format) {
101 11564 QStringList declarations;
102
6/6
✓ Branch 2 → 3 taken 11564 times.
✓ Branch 3 → 4 taken 11564 times.
✓ Branch 4 → 5 taken 11564 times.
✓ Branch 5 → 6 taken 11564 times.
✓ Branch 9 → 10 taken 59 times.
✓ Branch 9 → 19 taken 11505 times.
11564 if (const FontFamily *family = fontFamilyFor(format.fontFamilies().toStringList().value(0)))
103
2/2
✓ Branch 13 → 14 taken 59 times.
✓ Branch 14 → 15 taken 59 times.
118 declarations.append(QStringLiteral("font-family:") + family->stack);
104
3/3
✓ Branch 19 → 20 taken 11564 times.
✓ Branch 20 → 21 taken 58 times.
✓ Branch 20 → 34 taken 11506 times.
11564 if (format.hasProperty(QTextFormat::FontPointSize)) {
105
1/1
✓ Branch 21 → 22 taken 58 times.
58 const int points = qBound(FontPointSizeMin, qRound(format.fontPointSize()), FontPointSizeMax);
106
2/2
✓ Branch 28 → 29 taken 58 times.
✓ Branch 29 → 30 taken 58 times.
116 declarations.append(QStringLiteral("font-size:%1pt").arg(points));
107 }
108
7/8
✓ Branch 34 → 35 taken 11564 times.
✓ Branch 35 → 36 taken 9667 times.
✓ Branch 35 → 42 taken 1897 times.
✓ Branch 36 → 37 taken 9667 times.
✓ Branch 37 → 38 taken 41 times.
✓ Branch 37 → 42 taken 9626 times.
✓ Branch 40 → 41 taken 41 times.
✗ Branch 40 → 42 not taken.
11605 if (!format.isAnchor() && format.hasProperty(QTextFormat::ForegroundBrush) &&
109
5/7
✓ Branch 38 → 39 taken 41 times.
✓ Branch 43 → 44 taken 41 times.
✓ Branch 43 → 45 taken 11523 times.
✓ Branch 45 → 46 taken 41 times.
✓ Branch 45 → 60 taken 11523 times.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
11605 format.foreground().style() != Qt::NoBrush)
110
4/4
✓ Branch 46 → 47 taken 41 times.
✓ Branch 48 → 49 taken 41 times.
✓ Branch 52 → 53 taken 41 times.
✓ Branch 53 → 54 taken 41 times.
82 declarations.append(QStringLiteral("color:") + cssColor(format.foreground().color()));
111
8/12
✓ Branch 60 → 61 taken 11564 times.
✓ Branch 61 → 62 taken 14 times.
✓ Branch 61 → 70 taken 11550 times.
✓ Branch 62 → 63 taken 14 times.
✓ Branch 64 → 65 taken 14 times.
✗ Branch 64 → 70 not taken.
✓ Branch 68 → 69 taken 14 times.
✗ Branch 68 → 70 not taken.
✓ Branch 75 → 76 taken 14 times.
✓ Branch 75 → 90 taken 11550 times.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
23142 if (format.hasProperty(QTextFormat::BackgroundBrush) && format.background().style() != Qt::NoBrush &&
112
5/7
✓ Branch 65 → 66 taken 14 times.
✓ Branch 71 → 72 taken 14 times.
✓ Branch 71 → 73 taken 11550 times.
✓ Branch 73 → 74 taken 14 times.
✓ Branch 73 → 75 taken 11550 times.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 147 not taken.
11578 format.background().color().alpha() > 0)
113
4/4
✓ Branch 76 → 77 taken 14 times.
✓ Branch 78 → 79 taken 14 times.
✓ Branch 82 → 83 taken 14 times.
✓ Branch 83 → 84 taken 14 times.
28 declarations.append(QStringLiteral("background-color:") + cssColor(format.background().color()));
114
1/1
✓ Branch 92 → 93 taken 11564 times.
23128 return declarations.join(QLatin1Char(';'));
115 11564 }
116
117 class Writer {
118 public:
119 521 explicit Writer(const MailHtmlWriter::Options &options) : m_options(options) {}
120
121 521 MailHtmlWriter::Result run(const QTextDocument &document) {
122 521 writeFrame(document.rootFrame());
123 521 closeAll();
124 521 closeRegion();
125 2084 return {m_out, m_contentIds};
126 521 }
127
128 private:
129 struct OpenList {
130 QTextList *list = nullptr;
131 int level = 0;
132 QString tag;
133 };
134 const MailHtmlWriter::Options &m_options;
135 QString m_out;
136 QStringList m_contentIds;
137 QVector<OpenList> m_lists;
138 bool m_paragraphOpen = false;
139 ParagraphStyle m_paragraphStyle;
140 bool m_preOpen = false;
141 bool m_afterFrame = false;
142 int m_quoteDepth = 0;
143 int m_region = NoRegion;
144
145 521 void writeFrame(QTextFrame *frame) {
146
4/4
✓ Branch 2 → 3 taken 521 times.
✓ Branch 24 → 25 taken 4938 times.
✓ Branch 26 → 4 taken 4938 times.
✓ Branch 26 → 27 taken 521 times.
5459 for (auto it = frame->begin(); !it.atEnd(); ++it) {
147
2/2
✓ Branch 5 → 6 taken 55 times.
✓ Branch 5 → 12 taken 4883 times.
4938 if (QTextFrame *child = it.currentFrame()) {
148
1/1
✓ Branch 6 → 7 taken 55 times.
55 closeAll();
149
3/4
✓ Branch 7 → 8 taken 55 times.
✓ Branch 8 → 9 taken 55 times.
✗ Branch 8 → 10 not taken.
✓ Branch 9 → 11 taken 55 times.
55 if (auto *table = qobject_cast<QTextTable *>(child)) writeTable(table);
150 ✗ else writeFrame(child);
151 55 m_afterFrame = true;
152 55 continue;
153 55 }
154 // QTextDocument always keeps a block after a table; an empty one is
155 // structure, not a line the user typed.
156
1/1
✓ Branch 12 → 13 taken 4883 times.
4883 const QTextBlock block = it.currentBlock();
157
7/8
✓ Branch 13 → 14 taken 55 times.
✓ Branch 13 → 19 taken 4828 times.
✓ Branch 14 → 15 taken 55 times.
✓ Branch 15 → 16 taken 53 times.
✓ Branch 15 → 19 taken 2 times.
✓ Branch 16 → 17 taken 53 times.
✓ Branch 17 → 18 taken 53 times.
✗ Branch 17 → 19 not taken.
4883 const bool structural = m_afterFrame && block.length() <= 1 && !block.textList();
158 4883 m_afterFrame = false;
159
3/3
✓ Branch 20 → 21 taken 4830 times.
✓ Branch 20 → 22 taken 53 times.
✓ Branch 21 → 22 taken 4830 times.
4883 if (!structural) writeBlock(block);
160 }
161 521 }
162
163 4908 void closeParagraph() {
164
3/3
✓ Branch 2 → 3 taken 1858 times.
✓ Branch 2 → 10 taken 3050 times.
✓ Branch 6 → 7 taken 1858 times.
6766 if (m_paragraphOpen) m_out += QStringLiteral("</p>");
165 4908 m_paragraphOpen = false;
166 4908 }
167 5462 void closePre() {
168
3/3
✓ Branch 2 → 3 taken 15 times.
✓ Branch 2 → 10 taken 5447 times.
✓ Branch 6 → 7 taken 15 times.
5477 if (m_preOpen) m_out += QStringLiteral("</pre>");
169 5462 m_preOpen = false;
170 5462 }
171 5496 void closeLists(int downToLevel = 0) {
172
6/6
✓ Branch 18 → 19 taken 694 times.
✓ Branch 18 → 22 taken 4898 times.
✓ Branch 20 → 21 taken 96 times.
✓ Branch 20 → 22 taken 598 times.
✓ Branch 23 → 3 taken 96 times.
✓ Branch 23 → 24 taken 5496 times.
5592 while (!m_lists.isEmpty() && m_lists.last().level > downToLevel) {
173
4/4
✓ Branch 5 → 6 taken 96 times.
✓ Branch 9 → 10 taken 96 times.
✓ Branch 10 → 11 taken 96 times.
✓ Branch 11 → 12 taken 96 times.
192 m_out += QStringLiteral("</li></") + m_lists.last().tag + QLatin1Char('>');
174 96 m_lists.removeLast();
175 }
176 5496 }
177 660 void setQuoteDepth(int depth) {
178
2/2
✓ Branch 10 → 3 taken 32 times.
✓ Branch 10 → 11 taken 660 times.
692 while (m_quoteDepth > depth) {
179
1/1
✓ Branch 6 → 7 taken 32 times.
32 m_out += QStringLiteral("</blockquote>");
180 32 --m_quoteDepth;
181 }
182
2/2
✓ Branch 19 → 12 taken 32 times.
✓ Branch 19 → 20 taken 660 times.
692 while (m_quoteDepth < depth) {
183
1/1
✓ Branch 15 → 16 taken 32 times.
32 m_out += QStringLiteral("<blockquote style=\"margin:0 0 0 0.8ex;border-left:1px solid #cccccc;"
184 "padding-left:1ex\">");
185 32 ++m_quoteDepth;
186 }
187 660 }
188 620 void closeAll() {
189 620 closeParagraph();
190 620 closePre();
191 620 closeLists();
192 620 setQuoteDepth(0);
193 620 }
194 565 void closeRegion() {
195
3/3
✓ Branch 2 → 3 taken 42 times.
✓ Branch 2 → 10 taken 523 times.
✓ Branch 6 → 7 taken 42 times.
607 if (m_region != NoRegion) m_out += QStringLiteral("</div>");
196 565 m_region = NoRegion;
197 565 }
198 4830 void enterRegion(int region) {
199
2/2
✓ Branch 2 → 3 taken 4786 times.
✓ Branch 2 → 4 taken 44 times.
4830 if (region == m_region) return;
200 44 closeAll();
201 44 closeRegion();
202 // The only class values MailJD writes: fixed region markers that no CSS
203 // refers to (as Thunderbird's moz-signature); the import reads them back.
204
3/3
✓ Branch 6 → 7 taken 35 times.
✓ Branch 6 → 14 taken 9 times.
✓ Branch 10 → 11 taken 35 times.
79 if (region == SignatureRegion) m_out += QStringLiteral("<div class=\"mailjd-signature\">");
205
3/3
✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 22 taken 2 times.
✓ Branch 18 → 19 taken 7 times.
16 else if (region == QuoteRegion) m_out += QStringLiteral("<div class=\"mailjd-quote\">");
206
4/4
✓ Branch 22 → 23 taken 9 times.
✓ Branch 22 → 24 taken 35 times.
✓ Branch 23 → 24 taken 7 times.
✓ Branch 23 → 25 taken 2 times.
44 m_region = (region == SignatureRegion || region == QuoteRegion) ? region : NoRegion;
207 }
208
209 4830 void writeBlock(const QTextBlock &block) {
210
2/2
✓ Branch 2 → 3 taken 4830 times.
✓ Branch 3 → 4 taken 4830 times.
4830 enterRegion(ComposeDocumentFormat::region(block));
211
1/1
✓ Branch 4 → 5 taken 4830 times.
4830 const int depth = qBound(0, quoteLevel(block), 8);
212
2/2
✓ Branch 6 → 7 taken 40 times.
✓ Branch 6 → 11 taken 4790 times.
4830 if (depth != m_quoteDepth) {
213
1/1
✓ Branch 7 → 8 taken 40 times.
40 closeParagraph();
214
1/1
✓ Branch 8 → 9 taken 40 times.
40 closePre();
215
1/1
✓ Branch 9 → 10 taken 40 times.
40 closeLists();
216
1/1
✓ Branch 10 → 11 taken 40 times.
40 setQuoteDepth(depth);
217 }
218
3/3
✓ Branch 11 → 12 taken 4830 times.
✓ Branch 12 → 13 taken 28 times.
✓ Branch 12 → 37 taken 4802 times.
4830 if (isCodeBlock(block)) {
219
1/1
✓ Branch 13 → 14 taken 28 times.
28 closeParagraph();
220
1/1
✓ Branch 14 → 15 taken 28 times.
28 closeLists();
221
3/3
✓ Branch 15 → 16 taken 15 times.
✓ Branch 15 → 23 taken 13 times.
✓ Branch 19 → 20 taken 15 times.
43 if (!m_preOpen) m_out += QStringLiteral("<pre>");
222
1/1
✓ Branch 25 → 26 taken 13 times.
13 else m_out += QLatin1Char('\n');
223 28 m_preOpen = true;
224
1/1
✓ Branch 27 → 28 taken 28 times.
28 QString text = block.text();
225
1/1
✓ Branch 31 → 32 taken 28 times.
28 text.replace(QChar(0x2028), QLatin1Char('\n'));
226
2/2
✓ Branch 32 → 33 taken 28 times.
✓ Branch 33 → 34 taken 28 times.
28 m_out += text.toHtmlEscaped();
227 28 return;
228 28 }
229
1/1
✓ Branch 37 → 38 taken 4802 times.
4802 closePre();
230
9/12
✓ Branch 38 → 39 taken 4802 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 44 taken 4800 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 47 taken 4800 times.
✓ Branch 47 → 48 taken 2 times.
✓ Branch 47 → 57 taken 4800 times.
✗ Branch 204 → 205 not taken.
✗ Branch 204 → 206 not taken.
4802 if (isHorizontalRule(block) && block.text().isEmpty()) {
231
1/1
✓ Branch 48 → 49 taken 2 times.
2 closeParagraph();
232
1/1
✓ Branch 49 → 50 taken 2 times.
2 closeLists();
233
1/1
✓ Branch 53 → 54 taken 2 times.
2 m_out += QStringLiteral("<hr>");
234 2 return;
235 }
236
3/3
✓ Branch 57 → 58 taken 4800 times.
✓ Branch 58 → 59 taken 680 times.
✓ Branch 58 → 61 taken 4120 times.
4800 if (QTextList *list = block.textList()) {
237
1/1
✓ Branch 59 → 60 taken 680 times.
680 writeListItem(block, list);
238 680 return;
239 }
240
1/1
✓ Branch 61 → 62 taken 4120 times.
4120 closeLists();
241
2/2
✓ Branch 62 → 63 taken 4120 times.
✓ Branch 63 → 64 taken 4120 times.
4120 const int heading = qBound(0, block.blockFormat().headingLevel(), MaxHeadingLevel);
242
1/1
✓ Branch 66 → 67 taken 4120 times.
4120 const ParagraphStyle style = paragraphStyle(block);
243
2/2
✓ Branch 67 → 68 taken 67 times.
✓ Branch 67 → 110 taken 4053 times.
4120 if (heading > 0) {
244
1/1
✓ Branch 68 → 69 taken 67 times.
67 closeParagraph();
245
1/1
✓ Branch 73 → 74 taken 67 times.
134 const QString tag = QStringLiteral("h%1").arg(heading + 1);
246
4/4
✓ Branch 85 → 86 taken 67 times.
✓ Branch 88 → 89 taken 67 times.
✓ Branch 89 → 90 taken 67 times.
✓ Branch 90 → 91 taken 67 times.
134 m_out += QLatin1Char('<') + tag + blockAttributes(style, {}) + QLatin1Char('>') +
247
6/6
✓ Branch 81 → 82 taken 67 times.
✓ Branch 91 → 92 taken 67 times.
✓ Branch 92 → 93 taken 67 times.
✓ Branch 93 → 94 taken 67 times.
✓ Branch 94 → 95 taken 67 times.
✓ Branch 95 → 96 taken 67 times.
268 inlineContent(block) + QStringLiteral("</") + tag + QLatin1Char('>');
248 67 return;
249 67 }
250
3/3
✓ Branch 110 → 111 taken 4053 times.
✓ Branch 111 → 112 taken 1613 times.
✓ Branch 111 → 136 taken 2440 times.
4053 if (block.length() <= 1) {
251 // An empty block is an empty line of its own: paragraphs carry no
252 // margins, so what the receiver sees is what was typed.
253
1/1
✓ Branch 112 → 113 taken 1613 times.
1613 closeParagraph();
254
4/4
✓ Branch 119 → 120 taken 1613 times.
✓ Branch 123 → 124 taken 1613 times.
✓ Branch 124 → 125 taken 1613 times.
✓ Branch 125 → 126 taken 1613 times.
4839 m_out += QStringLiteral("<p") + blockAttributes(style, QStringLiteral("margin:0")) + QStringLiteral("><br></p>");
255 1613 return;
256 }
257
6/6
✓ Branch 136 → 137 taken 585 times.
✓ Branch 136 → 140 taken 1855 times.
✓ Branch 138 → 139 taken 582 times.
✓ Branch 138 → 140 taken 3 times.
✓ Branch 141 → 142 taken 582 times.
✓ Branch 141 → 153 taken 1858 times.
2440 if (m_paragraphOpen && m_paragraphStyle == style) {
258
3/3
✓ Branch 142 → 143 taken 582 times.
✓ Branch 146 → 147 taken 582 times.
✓ Branch 147 → 148 taken 582 times.
1164 m_out += QStringLiteral("<br>") + inlineContent(block);
259 582 return;
260 }
261
1/1
✓ Branch 153 → 154 taken 1858 times.
1858 closeParagraph();
262
2/2
✓ Branch 160 → 161 taken 1858 times.
✓ Branch 164 → 165 taken 1858 times.
5574 m_out += QStringLiteral("<p") + blockAttributes(style, QStringLiteral("margin:0")) +
263
4/4
✓ Branch 154 → 155 taken 1858 times.
✓ Branch 165 → 166 taken 1858 times.
✓ Branch 166 → 167 taken 1858 times.
✓ Branch 167 → 168 taken 1858 times.
5574 QLatin1Char('>') + inlineContent(block);
264 1858 m_paragraphOpen = true;
265 1858 m_paragraphStyle = style;
266 4120 }
267
268 96 static QString listTag(const QTextListFormat &format, QString &styleType) {
269
2/8
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 15 not taken.
✓ Branch 3 → 26 taken 62 times.
✗ Branch 3 → 37 not taken.
✗ Branch 3 → 48 not taken.
✗ Branch 3 → 59 not taken.
✗ Branch 3 → 70 not taken.
✓ Branch 3 → 81 taken 34 times.
96 switch (format.style()) {
270 ✗ case QTextListFormat::ListCircle: styleType = QStringLiteral("circle"); return QStringLiteral("ul");
271 ✗ case QTextListFormat::ListSquare: styleType = QStringLiteral("square"); return QStringLiteral("ul");
272 186 case QTextListFormat::ListDecimal: styleType = QStringLiteral("decimal"); return QStringLiteral("ol");
273 ✗ case QTextListFormat::ListLowerAlpha: styleType = QStringLiteral("lower-alpha"); return QStringLiteral("ol");
274 ✗ case QTextListFormat::ListUpperAlpha: styleType = QStringLiteral("upper-alpha"); return QStringLiteral("ol");
275 ✗ case QTextListFormat::ListLowerRoman: styleType = QStringLiteral("lower-roman"); return QStringLiteral("ol");
276 ✗ case QTextListFormat::ListUpperRoman: styleType = QStringLiteral("upper-roman"); return QStringLiteral("ol");
277 102 default: styleType = QStringLiteral("disc"); return QStringLiteral("ul");
278 }
279 }
280
281 680 void writeListItem(const QTextBlock &block, QTextList *list) {
282
1/1
✓ Branch 2 → 3 taken 680 times.
680 closeParagraph();
283
2/2
✓ Branch 3 → 4 taken 680 times.
✓ Branch 4 → 5 taken 680 times.
680 const int level = qBound(1, list->format().indent(), MaxListLevel);
284
1/1
✓ Branch 7 → 8 taken 680 times.
680 closeLists(level);
285
10/10
✓ Branch 9 → 10 taken 598 times.
✓ Branch 9 → 15 taken 82 times.
✓ Branch 10 → 11 taken 598 times.
✓ Branch 11 → 12 taken 590 times.
✓ Branch 11 → 15 taken 8 times.
✓ Branch 12 → 13 taken 590 times.
✓ Branch 13 → 14 taken 6 times.
✓ Branch 13 → 15 taken 584 times.
✓ Branch 16 → 17 taken 6 times.
✓ Branch 16 → 18 taken 674 times.
680 if (!m_lists.isEmpty() && m_lists.last().level == level && m_lists.last().list != list)
286
1/1
✓ Branch 17 → 18 taken 6 times.
6 closeLists(level - 1);
287
7/7
✓ Branch 19 → 20 taken 592 times.
✓ Branch 19 → 22 taken 88 times.
✓ Branch 20 → 21 taken 592 times.
✓ Branch 21 → 22 taken 8 times.
✓ Branch 21 → 23 taken 584 times.
✓ Branch 24 → 25 taken 96 times.
✓ Branch 24 → 55 taken 584 times.
680 if (m_lists.isEmpty() || m_lists.last().level < level) {
288 96 QString styleType;
289
2/2
✓ Branch 25 → 26 taken 96 times.
✓ Branch 26 → 27 taken 96 times.
96 const QString tag = listTag(list->format(), styleType);
290 // A deeper list opens inside the open item; a list without a parent
291 // item at a deeper level starts at its own level.
292
3/3
✓ Branch 36 → 37 taken 96 times.
✓ Branch 37 → 38 taken 96 times.
✓ Branch 38 → 39 taken 96 times.
192 m_out += QLatin1Char('<') + tag + QStringLiteral(" style=\"list-style-type:") + styleType +
293
2/2
✓ Branch 39 → 40 taken 96 times.
✓ Branch 40 → 41 taken 96 times.
288 QStringLiteral(";margin-top:0;margin-bottom:0\">");
294
1/1
✓ Branch 50 → 51 taken 96 times.
96 m_lists.append({list, level, tag});
295 96 } else {
296
1/1
✓ Branch 58 → 59 taken 584 times.
584 m_out += QStringLiteral("</li>");
297 }
298
1/1
✓ Branch 62 → 63 taken 680 times.
680 const ParagraphStyle style = paragraphStyle(block);
299
6/6
✓ Branch 63 → 64 taken 680 times.
✓ Branch 67 → 68 taken 680 times.
✓ Branch 71 → 72 taken 680 times.
✓ Branch 72 → 73 taken 680 times.
✓ Branch 73 → 74 taken 680 times.
✓ Branch 74 → 75 taken 680 times.
1360 m_out += QStringLiteral("<li") + blockAttributes(style, {}) + QLatin1Char('>') + inlineContent(block);
300 776 }
301
302 55 void writeTable(QTextTable *table) {
303
1/1
✓ Branch 2 → 3 taken 55 times.
55 const QTextTableFormat format = table->format();
304
2/2
✓ Branch 3 → 4 taken 55 times.
✓ Branch 4 → 5 taken 55 times.
55 const int headerRows = qBound(0, format.headerRowCount(), table->rows());
305
1/1
✓ Branch 6 → 7 taken 55 times.
55 const bool border = format.border() > 0;
306 110 m_out += QStringLiteral("<table cellpadding=\"4\" cellspacing=\"0\" border=\"%1\" style=\"border-collapse:"
307 "collapse%2\">")
308
3/3
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 53 times.
✓ Branch 14 → 15 taken 55 times.
110 .arg(border ? 1 : 0)
309
6/8
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 20 taken 53 times.
✓ Branch 21 → 22 taken 55 times.
✓ Branch 22 → 23 taken 55 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 27 taken 53 times.
✗ Branch 237 → 238 not taken.
✗ Branch 237 → 239 not taken.
57 .arg(border ? QStringLiteral(";border:1px solid #c8c8c8") : QString());
310
1/1
✓ Branch 30 → 31 taken 55 times.
55 const auto widths = format.columnWidthConstraints();
311
3/3
✓ Branch 211 → 212 taken 189 times.
✓ Branch 212 → 32 taken 134 times.
✓ Branch 212 → 213 taken 55 times.
189 for (int row = 0; row < table->rows(); ++row) {
312
9/13
✓ Branch 32 → 33 taken 55 times.
✓ Branch 32 → 47 taken 79 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 37 taken 53 times.
✓ Branch 40 → 41 taken 55 times.
✓ Branch 42 → 43 taken 53 times.
✓ Branch 42 → 44 taken 2 times.
✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 46 taken 53 times.
✗ Branch 256 → 257 not taken.
✗ Branch 256 → 258 not taken.
✗ Branch 260 → 261 not taken.
✗ Branch 260 → 262 not taken.
189 if (row == 0) m_out += headerRows > 0 ? QStringLiteral("<thead>") : QStringLiteral("<tbody>");
313
5/5
✓ Branch 47 → 48 taken 55 times.
✓ Branch 47 → 56 taken 79 times.
✓ Branch 48 → 49 taken 2 times.
✓ Branch 48 → 56 taken 53 times.
✓ Branch 52 → 53 taken 2 times.
136 if (row == headerRows && headerRows > 0) m_out += QStringLiteral("</thead><tbody>");
314
1/1
✓ Branch 59 → 60 taken 134 times.
134 m_out += QStringLiteral("<tr>");
315
3/3
✓ Branch 202 → 203 taken 386 times.
✓ Branch 203 → 63 taken 252 times.
✓ Branch 203 → 204 taken 134 times.
386 for (int column = 0; column < table->columns(); ++column) {
316
1/1
✓ Branch 63 → 64 taken 252 times.
252 const QTextTableCell cell = table->cellAt(row, column);
317 // A merged cell is written once, where it starts (pasted tables).
318
8/10
✓ Branch 65 → 66 taken 252 times.
✗ Branch 65 → 70 not taken.
✓ Branch 66 → 67 taken 252 times.
✓ Branch 67 → 68 taken 251 times.
✓ Branch 67 → 70 taken 1 time.
✓ Branch 68 → 69 taken 251 times.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 251 times.
✓ Branch 72 → 73 taken 1 time.
✓ Branch 72 → 74 taken 251 times.
252 if (!cell.isValid() || cell.row() != row || cell.column() != column) continue;
319
6/6
✓ Branch 74 → 75 taken 4 times.
✓ Branch 74 → 78 taken 247 times.
✓ Branch 81 → 82 taken 247 times.
✓ Branch 81 → 83 taken 4 times.
✓ Branch 83 → 84 taken 4 times.
✓ Branch 83 → 85 taken 247 times.
502 const QString tag = row < headerRows ? QStringLiteral("th") : QStringLiteral("td");
320 251 QString attributes;
321
2/6
✓ Branch 85 → 86 taken 251 times.
✗ Branch 86 → 87 not taken.
✓ Branch 86 → 99 taken 251 times.
✗ Branch 91 → 92 not taken.
✗ Branch 93 → 94 not taken.
✗ Branch 94 → 95 not taken.
251 if (cell.columnSpan() > 1) attributes += QStringLiteral(" colspan=\"%1\"").arg(qMin(cell.columnSpan(), 64));
322
2/6
✓ Branch 99 → 100 taken 251 times.
✗ Branch 100 → 101 not taken.
✓ Branch 100 → 113 taken 251 times.
✗ Branch 105 → 106 not taken.
✗ Branch 107 → 108 not taken.
✗ Branch 108 → 109 not taken.
251 if (cell.rowSpan() > 1) attributes += QStringLiteral(" rowspan=\"%1\"").arg(qMin(cell.rowSpan(), 64));
323
7/8
✓ Branch 113 → 114 taken 99 times.
✓ Branch 113 → 120 taken 152 times.
✓ Branch 115 → 116 taken 99 times.
✗ Branch 115 → 120 not taken.
✓ Branch 118 → 119 taken 4 times.
✓ Branch 118 → 120 taken 95 times.
✓ Branch 121 → 122 taken 4 times.
✓ Branch 121 → 136 taken 247 times.
251 if (row == 0 && column < widths.size() && widths.at(column).type() == QTextLength::PercentageLength)
324
2/2
✓ Branch 130 → 131 taken 4 times.
✓ Branch 131 → 132 taken 4 times.
8 attributes += QStringLiteral(" width=\"%1%\"").arg(qBound(1, qRound(widths.at(column).rawValue()), 100));
325
3/3
✓ Branch 136 → 137 taken 8 times.
✓ Branch 136 → 144 taken 243 times.
✓ Branch 140 → 141 taken 8 times.
259 if (border) attributes += QStringLiteral(" style=\"border:1px solid #c8c8c8\"");
326
4/4
✓ Branch 148 → 149 taken 251 times.
✓ Branch 149 → 150 taken 251 times.
✓ Branch 150 → 151 taken 251 times.
✓ Branch 151 → 152 taken 251 times.
251 m_out += QLatin1Char('<') + tag + attributes + QLatin1Char('>');
327 // The cell's blocks by position: a nested table's frame iterator did
328 // not end inside a merged cell (fuzz_compose_html_import). Nested
329 // tables are not offered (D-6); their lines join the cell.
330 251 bool first = true;
331
1/1
✓ Branch 155 → 156 taken 251 times.
251 const QTextDocument *document = table->document();
332
2/2
✓ Branch 156 → 157 taken 251 times.
✓ Branch 157 → 158 taken 251 times.
251 for (QTextBlock block = document->findBlock(cell.firstPosition());
333
8/9
✓ Branch 173 → 174 taken 514 times.
✓ Branch 174 → 175 taken 514 times.
✗ Branch 174 → 179 not taken.
✓ Branch 175 → 176 taken 514 times.
✓ Branch 176 → 177 taken 514 times.
✓ Branch 177 → 178 taken 263 times.
✓ Branch 177 → 179 taken 251 times.
✓ Branch 180 → 159 taken 263 times.
✓ Branch 180 → 181 taken 251 times.
514 block.isValid() && block.position() <= cell.lastPosition(); block = block.next()) {
334
3/3
✓ Branch 159 → 160 taken 12 times.
✓ Branch 159 → 167 taken 251 times.
✓ Branch 163 → 164 taken 12 times.
275 if (!first) m_out += QStringLiteral("<br>");
335
2/2
✓ Branch 167 → 168 taken 263 times.
✓ Branch 168 → 169 taken 263 times.
263 m_out += inlineContent(block);
336
1/1
✓ Branch 170 → 171 taken 263 times.
263 first = false;
337 }
338
3/3
✓ Branch 186 → 187 taken 251 times.
✓ Branch 187 → 188 taken 251 times.
✓ Branch 188 → 189 taken 251 times.
502 m_out += QStringLiteral("</") + tag + QLatin1Char('>');
339 252 }
340
1/1
✓ Branch 207 → 208 taken 134 times.
134 m_out += QStringLiteral("</tr>");
341 }
342
2/7
✓ Branch 213 → 214 taken 55 times.
✗ Branch 214 → 215 not taken.
✓ Branch 214 → 219 taken 55 times.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 219 not taken.
✗ Branch 377 → 378 not taken.
✗ Branch 377 → 379 not taken.
165 m_out += (headerRows >= table->rows() && headerRows > 0) ? QStringLiteral("</thead></table>")
343
3/7
✓ Branch 222 → 223 taken 55 times.
✓ Branch 224 → 225 taken 55 times.
✗ Branch 224 → 226 not taken.
✗ Branch 226 → 227 not taken.
✓ Branch 226 → 228 taken 55 times.
✗ Branch 373 → 374 not taken.
✗ Branch 373 → 375 not taken.
110 : QStringLiteral("</tbody></table>");
344 55 }
345
346 30 QString imageHtml(const QTextImageFormat &image) {
347
6/8
✓ Branch 3 → 4 taken 28 times.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 4 → 5 taken 28 times.
✓ Branch 5 → 7 taken 28 times.
✓ Branch 7 → 8 taken 28 times.
✓ Branch 7 → 9 taken 2 times.
✗ Branch 123 → 124 not taken.
✗ Branch 123 → 125 not taken.
30 const QString contentId = m_options.contentIdFor ? m_options.contentIdFor(image.name()) : QString();
348
5/5
✓ Branch 9 → 10 taken 30 times.
✓ Branch 10 → 11 taken 30 times.
✓ Branch 11 → 12 taken 28 times.
✓ Branch 11 → 13 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
30 QString alt = image.name().startsWith(ImageScheme) ? QString() : image.name();
349
4/4
✓ Branch 15 → 16 taken 30 times.
✓ Branch 16 → 17 taken 28 times.
✓ Branch 16 → 21 taken 2 times.
✓ Branch 17 → 18 taken 28 times.
30 if (image.hasProperty(ImageAltProperty)) alt = image.stringProperty(ImageAltProperty);
350
8/10
✓ Branch 22 → 23 taken 26 times.
✓ Branch 22 → 31 taken 4 times.
✓ Branch 25 → 26 taken 26 times.
✓ Branch 26 → 27 taken 26 times.
✗ Branch 26 → 31 not taken.
✓ Branch 29 → 30 taken 26 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 26 times.
✓ Branch 33 → 34 taken 4 times.
✓ Branch 33 → 59 taken 26 times.
30 if (contentId.isEmpty() || contentId.contains(QLatin1Char('"')) || contentId.contains(QLatin1Char('>')))
351
9/25
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 4 times.
✓ Branch 43 → 44 taken 4 times.
✓ Branch 44 → 45 taken 4 times.
✓ Branch 45 → 46 taken 4 times.
✓ Branch 48 → 49 taken 4 times.
✗ Branch 48 → 50 not taken.
✓ Branch 50 → 51 taken 4 times.
✗ Branch 50 → 52 not taken.
✓ Branch 52 → 53 taken 4 times.
✗ Branch 52 → 54 not taken.
✓ Branch 54 → 55 taken 4 times.
✗ Branch 54 → 56 not taken.
✓ Branch 56 → 57 taken 4 times.
✗ Branch 56 → 58 not taken.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 147 not taken.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
✗ Branch 153 → 154 not taken.
✗ Branch 153 → 155 not taken.
12 return escapedText(alt.isEmpty() ? QString() : QStringLiteral("[") + alt + QStringLiteral("]"));
352
2/3
✓ Branch 60 → 61 taken 26 times.
✗ Branch 60 → 62 not taken.
✓ Branch 61 → 62 taken 26 times.
26 if (!m_contentIds.contains(contentId)) m_contentIds.append(contentId);
353
3/3
✓ Branch 68 → 69 taken 26 times.
✓ Branch 72 → 73 taken 26 times.
✓ Branch 73 → 74 taken 26 times.
78 QString html = QStringLiteral("<img src=\"cid:") + contentId.toHtmlEscaped() + QStringLiteral("\" alt=\"") +
354
3/3
✓ Branch 64 → 65 taken 26 times.
✓ Branch 74 → 75 taken 26 times.
✓ Branch 75 → 76 taken 26 times.
78 alt.toHtmlEscaped() + QLatin1Char('"');
355
6/6
✓ Branch 85 → 86 taken 26 times.
✓ Branch 86 → 87 taken 18 times.
✓ Branch 86 → 100 taken 8 times.
✓ Branch 91 → 92 taken 18 times.
✓ Branch 94 → 95 taken 18 times.
✓ Branch 95 → 96 taken 18 times.
62 if (image.width() > 0) html += QStringLiteral(" width=\"%1\"").arg(qBound(1, qRound(image.width()), 4096));
356
2/6
✓ Branch 100 → 101 taken 26 times.
✗ Branch 101 → 102 not taken.
✓ Branch 101 → 115 taken 26 times.
✗ Branch 106 → 107 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 110 → 111 not taken.
26 if (image.height() > 0) html += QStringLiteral(" height=\"%1\"").arg(qBound(1, qRound(image.height()), 4096));
357
1/1
✓ Branch 117 → 118 taken 26 times.
26 return html + QLatin1Char('>');
358 30 }
359
360 // Headings are bold by themselves; their bold is not repeated.
361 3450 QString inlineContent(const QTextBlock &block) {
362
2/2
✓ Branch 2 → 3 taken 3450 times.
✓ Branch 3 → 4 taken 3450 times.
3450 const bool heading = block.blockFormat().headingLevel() > 0;
363 3450 bool previousSpace = false;
364 3450 QString out;
365
4/4
✓ Branch 5 → 6 taken 3450 times.
✓ Branch 246 → 247 taken 11594 times.
✓ Branch 248 → 7 taken 11594 times.
✓ Branch 248 → 249 taken 3450 times.
15044 for (auto it = block.begin(); !it.atEnd(); ++it) {
366
1/1
✓ Branch 7 → 8 taken 11594 times.
11594 const QTextFragment fragment = it.fragment();
367
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 11594 times.
11624 if (!fragment.isValid()) continue;
368
1/1
✓ Branch 11 → 12 taken 11594 times.
11594 const QTextCharFormat format = fragment.charFormat();
369
3/3
✓ Branch 12 → 13 taken 11594 times.
✓ Branch 13 → 14 taken 30 times.
✓ Branch 13 → 26 taken 11564 times.
11594 if (format.isImageFormat()) {
370 // One object character per image; a fragment can hold several.
371
6/6
✓ Branch 15 → 16 taken 30 times.
✓ Branch 16 → 17 taken 30 times.
✓ Branch 17 → 18 taken 30 times.
✓ Branch 21 → 22 taken 60 times.
✓ Branch 24 → 15 taken 30 times.
✓ Branch 24 → 25 taken 30 times.
60 for (int i = 0; i < fragment.text().size(); ++i) out += imageHtml(format.toImageFormat());
372 30 previousSpace = false;
373 30 continue;
374 30 }
375
2/2
✓ Branch 26 → 27 taken 11564 times.
✓ Branch 27 → 28 taken 11564 times.
11564 QString text = escapedText(fragment.text(), previousSpace);
376
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 11564 times.
11564 if (text.isEmpty()) continue;
377
5/5
✓ Branch 32 → 33 taken 11564 times.
✓ Branch 33 → 34 taken 16 times.
✓ Branch 33 → 50 taken 11548 times.
✓ Branch 40 → 41 taken 16 times.
✓ Branch 41 → 42 taken 16 times.
11596 if (isInlineCode(format)) text = QStringLiteral("<code>") + text + QStringLiteral("</code>");
378
3/3
✓ Branch 50 → 51 taken 11564 times.
✓ Branch 51 → 52 taken 4 times.
✓ Branch 51 → 68 taken 11560 times.
11564 if (format.verticalAlignment() == QTextCharFormat::AlignSuperScript)
379
2/2
✓ Branch 58 → 59 taken 4 times.
✓ Branch 59 → 60 taken 4 times.
8 text = QStringLiteral("<sup>") + text + QStringLiteral("</sup>");
380
2/3
✓ Branch 68 → 69 taken 11560 times.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 86 taken 11560 times.
11560 else if (format.verticalAlignment() == QTextCharFormat::AlignSubScript)
381 ✗ text = QStringLiteral("<sub>") + text + QStringLiteral("</sub>");
382
5/5
✓ Branch 86 → 87 taken 11564 times.
✓ Branch 87 → 88 taken 9 times.
✓ Branch 87 → 104 taken 11555 times.
✓ Branch 94 → 95 taken 9 times.
✓ Branch 95 → 96 taken 9 times.
11582 if (format.fontStrikeOut()) text = QStringLiteral("<s>") + text + QStringLiteral("</s>");
383
10/10
✓ Branch 104 → 105 taken 11564 times.
✓ Branch 105 → 106 taken 12 times.
✓ Branch 105 → 109 taken 11552 times.
✓ Branch 106 → 107 taken 12 times.
✓ Branch 107 → 108 taken 10 times.
✓ Branch 107 → 109 taken 2 times.
✓ Branch 110 → 111 taken 10 times.
✓ Branch 110 → 127 taken 11554 times.
✓ Branch 117 → 118 taken 10 times.
✓ Branch 118 → 119 taken 10 times.
11584 if (format.fontUnderline() && !format.isAnchor()) text = QStringLiteral("<u>") + text + QStringLiteral("</u>");
384
5/5
✓ Branch 127 → 128 taken 11564 times.
✓ Branch 128 → 129 taken 1210 times.
✓ Branch 128 → 145 taken 10354 times.
✓ Branch 135 → 136 taken 1210 times.
✓ Branch 136 → 137 taken 1210 times.
13984 if (format.fontItalic()) text = QStringLiteral("<em>") + text + QStringLiteral("</em>");
385
7/7
✓ Branch 145 → 146 taken 11564 times.
✓ Branch 146 → 147 taken 1374 times.
✓ Branch 146 → 149 taken 10190 times.
✓ Branch 147 → 148 taken 1372 times.
✓ Branch 147 → 149 taken 2 times.
✓ Branch 150 → 151 taken 1372 times.
✓ Branch 150 → 167 taken 10192 times.
11564 if (format.fontWeight() >= QFont::Bold && !heading)
386
2/2
✓ Branch 157 → 158 taken 1372 times.
✓ Branch 158 → 159 taken 1372 times.
2744 text = QStringLiteral("<strong>") + text + QStringLiteral("</strong>");
387
1/1
✓ Branch 167 → 168 taken 11564 times.
11564 const QString style = spanStyle(format);
388
6/6
✓ Branch 169 → 170 taken 123 times.
✓ Branch 169 → 195 taken 11441 times.
✓ Branch 179 → 180 taken 123 times.
✓ Branch 180 → 181 taken 123 times.
✓ Branch 181 → 182 taken 123 times.
✓ Branch 182 → 183 taken 123 times.
11933 if (!style.isEmpty()) text = QStringLiteral("<span style=\"") + style + QStringLiteral("\">") + text + QStringLiteral("</span>");
389
11/13
✓ Branch 195 → 196 taken 11564 times.
✓ Branch 196 → 197 taken 1897 times.
✓ Branch 196 → 201 taken 9667 times.
✓ Branch 197 → 198 taken 1897 times.
✓ Branch 198 → 199 taken 1897 times.
✓ Branch 199 → 200 taken 1895 times.
✓ Branch 199 → 201 taken 2 times.
✓ Branch 202 → 203 taken 1897 times.
✓ Branch 202 → 204 taken 9667 times.
✓ Branch 204 → 205 taken 1895 times.
✓ Branch 204 → 234 taken 9669 times.
✗ Branch 404 → 405 not taken.
✗ Branch 404 → 406 not taken.
11564 if (format.isAnchor() && isAllowedLink(format.anchorHref()))
390
5/5
✓ Branch 211 → 212 taken 1895 times.
✓ Branch 212 → 213 taken 1895 times.
✓ Branch 216 → 217 taken 1895 times.
✓ Branch 217 → 218 taken 1895 times.
✓ Branch 218 → 219 taken 1895 times.
5685 text = QStringLiteral("<a href=\"") + format.anchorHref().toHtmlEscaped() + QStringLiteral("\">") + text +
391
1/1
✓ Branch 219 → 220 taken 1895 times.
5685 QStringLiteral("</a>");
392
1/1
✓ Branch 234 → 235 taken 11564 times.
11564 out += text;
393 11594 }
394 3450 return out;
395 ✗ }
396 };
397
398 } // namespace
399
400 521 MailHtmlWriter::Result MailHtmlWriter::writeBody(const QTextDocument &document, const Options &options) {
401 521 Writer writer(options);
402
1/1
✓ Branch 3 → 4 taken 521 times.
1042 return writer.run(document);
403 521 }
404
405 497 QString MailHtmlWriter::wrapDocument(const QString &bodyHtml, bool rightToLeft) {
406 994 return QStringLiteral("<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\">"
407 "<meta name=\"viewport\" content=\"width=device-width\"></head>\n"
408 "<body><div%1 style=\"font-family:system-ui,-apple-system,'Segoe UI',Roboto,"
409 "Helvetica,Arial,sans-serif;max-width:44em\">")
410
5/7
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 13 taken 495 times.
✓ Branch 14 → 15 taken 497 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 23 taken 495 times.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
996 .arg(rightToLeft ? QStringLiteral(" dir=\"rtl\"") : QString()) +
411
2/2
✓ Branch 15 → 16 taken 497 times.
✓ Branch 16 → 17 taken 497 times.
1491 bodyHtml + QStringLiteral("</div></body></html>\n");
412 }
413
414 12 QString MailHtmlWriter::quoteContainer(const QString &attributionText, const QString &quotedBodyHtml) {
415
2/2
✓ Branch 8 → 9 taken 12 times.
✓ Branch 12 → 13 taken 12 times.
36 return QStringLiteral("<div class=\"mailjd-quote\"><p style=\"margin:0\">") + escapedText(attributionText) +
416
1/1
✓ Branch 13 → 14 taken 12 times.
36 QStringLiteral("</p><blockquote style=\"margin:0 0 0 0.8ex;border-left:1px solid #cccccc;"
417 12 "padding-left:1ex\">") +
418
2/2
✓ Branch 14 → 15 taken 12 times.
✓ Branch 15 → 16 taken 12 times.
36 quotedBodyHtml + QStringLiteral("</blockquote></div>");
419 }
420
421 namespace {
422 8 bool fail(QString *problem, const QString &text) {
423
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 7 times.
8 if (problem) *problem = text;
424 8 return false;
425 }
426 } // namespace
427
428 63 bool MailHtmlWriter::conforms(const QString &html, QString *problem) {
429 63 qsizetype position = 0;
430 while (true) {
431 1548 const qsizetype open = html.indexOf(QLatin1Char('<'), position);
432
2/2
✓ Branch 6 → 7 taken 55 times.
✓ Branch 6 → 8 taken 1493 times.
1556 if (open < 0) return true;
433 1493 const qsizetype close = html.indexOf(QLatin1Char('>'), open);
434
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 19 taken 1493 times.
1493 if (close < 0) return fail(problem, QStringLiteral("unterminated tag"));
435
1/1
✓ Branch 19 → 20 taken 1493 times.
1493 const QString tag = html.mid(open + 1, close - open - 1);
436 1493 position = close + 1;
437
3/3
✓ Branch 21 → 22 taken 1493 times.
✓ Branch 22 → 23 taken 54 times.
✓ Branch 22 → 24 taken 1439 times.
1493 if (tag.startsWith(QLatin1String("!DOCTYPE html"))) continue;
438
3/3
✓ Branch 26 → 27 taken 1439 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 35 taken 1438 times.
1440 if (tag.startsWith(QLatin1Char('!'))) return fail(problem, QStringLiteral("comment or declaration"));
439 1438 QString rest = tag;
440
4/4
✓ Branch 38 → 39 taken 1438 times.
✓ Branch 39 → 40 taken 598 times.
✓ Branch 39 → 44 taken 840 times.
✓ Branch 40 → 41 taken 598 times.
1438 if (rest.startsWith(QLatin1Char('/'))) rest = rest.mid(1);
441 1438 qsizetype nameEnd = 0;
442
6/6
✓ Branch 47 → 48 taken 4199 times.
✓ Branch 47 → 52 taken 961 times.
✓ Branch 50 → 51 taken 3722 times.
✓ Branch 50 → 52 taken 477 times.
✓ Branch 53 → 45 taken 3722 times.
✓ Branch 53 → 54 taken 1438 times.
5160 while (nameEnd < rest.size() && rest.at(nameEnd).isLetterOrNumber()) ++nameEnd;
443
2/2
✓ Branch 54 → 55 taken 1438 times.
✓ Branch 55 → 56 taken 1438 times.
1438 const QString name = rest.left(nameEnd).toLower();
444
4/4
✓ Branch 57 → 58 taken 1438 times.
✓ Branch 59 → 60 taken 1 time.
✓ Branch 59 → 69 taken 1437 times.
✓ Branch 63 → 64 taken 1 time.
1439 if (!allowedElements().contains(name)) return fail(problem, QStringLiteral("element ") + name);
445 // Attributes: name="value" pairs only (the writer always quotes).
446 1437 qsizetype at = nameEnd;
447
2/2
✓ Branch 293 → 70 taken 554 times.
✓ Branch 293 → 294 taken 1431 times.
1985 while (at < rest.size()) {
448
5/6
✓ Branch 73 → 74 taken 1108 times.
✗ Branch 73 → 78 not taken.
✓ Branch 76 → 77 taken 554 times.
✓ Branch 76 → 78 taken 554 times.
✓ Branch 79 → 71 taken 554 times.
✓ Branch 79 → 80 taken 554 times.
1108 while (at < rest.size() && rest.at(at).isSpace()) ++at;
449
1/2
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 554 times.
554 if (at >= rest.size()) break;
450 554 const qsizetype equals = rest.indexOf(QLatin1Char('='), at);
451
4/8
✓ Branch 86 → 87 taken 554 times.
✗ Branch 86 → 94 not taken.
✓ Branch 88 → 89 taken 554 times.
✗ Branch 88 → 94 not taken.
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 554 times.
✗ Branch 96 → 97 not taken.
✓ Branch 96 → 106 taken 554 times.
554 if (equals < 0 || equals + 1 >= rest.size() || rest.at(equals + 1) != QLatin1Char('"'))
452 ✗ return fail(problem, QStringLiteral("unquoted attribute in ") + name);
453
3/3
✓ Branch 106 → 107 taken 554 times.
✓ Branch 107 → 108 taken 554 times.
✓ Branch 108 → 109 taken 554 times.
554 const QString attribute = rest.mid(at, equals - at).trimmed().toLower();
454 554 const qsizetype valueEnd = rest.indexOf(QLatin1Char('"'), equals + 2);
455
1/2
✗ Branch 114 → 115 not taken.
✓ Branch 114 → 122 taken 554 times.
554 if (valueEnd < 0) return fail(problem, QStringLiteral("unterminated attribute"));
456
1/1
✓ Branch 122 → 123 taken 554 times.
554 const QString value = rest.mid(equals + 2, valueEnd - equals - 2);
457 554 at = valueEnd + 1;
458
4/4
✓ Branch 123 → 124 taken 554 times.
✓ Branch 125 → 126 taken 1 time.
✓ Branch 125 → 135 taken 553 times.
✓ Branch 129 → 130 taken 1 time.
555 if (!allowedAttributes().contains(attribute)) return fail(problem, QStringLiteral("attribute ") + attribute);
459
2/2
✓ Branch 137 → 138 taken 140 times.
✓ Branch 137 → 167 taken 413 times.
553 if (attribute == QLatin1String("href")) {
460 140 QString href = value;
461
1/1
✓ Branch 145 → 146 taken 140 times.
280 href.replace(QStringLiteral("&amp;"), QStringLiteral("&"));
462
4/4
✓ Branch 150 → 151 taken 140 times.
✓ Branch 151 → 152 taken 1 time.
✓ Branch 151 → 161 taken 139 times.
✓ Branch 155 → 156 taken 1 time.
141 if (!ComposeDocumentFormat::isAllowedLink(href)) return fail(problem, QStringLiteral("link ") + value);
463 140 }
464
4/4
✓ Branch 169 → 170 taken 5 times.
✓ Branch 169 → 177 taken 547 times.
✓ Branch 172 → 173 taken 2 times.
✓ Branch 172 → 177 taken 3 times.
554 if (attribute == QLatin1String("class") && value != QLatin1String("mailjd-signature") &&
465
4/4
✓ Branch 175 → 176 taken 1 time.
✓ Branch 175 → 177 taken 1 time.
✓ Branch 178 → 179 taken 1 time.
✓ Branch 178 → 188 taken 551 times.
554 value != QLatin1String("mailjd-quote"))
466
1/1
✓ Branch 182 → 183 taken 1 time.
1 return fail(problem, QStringLiteral("class ") + value);
467
7/7
✓ Branch 190 → 191 taken 3 times.
✓ Branch 190 → 195 taken 548 times.
✓ Branch 192 → 193 taken 3 times.
✓ Branch 193 → 194 taken 1 time.
✓ Branch 193 → 195 taken 2 times.
✓ Branch 196 → 197 taken 1 time.
✓ Branch 196 → 206 taken 550 times.
551 if (attribute == QLatin1String("src") && !value.startsWith(QLatin1String("cid:")))
468
1/1
✓ Branch 200 → 201 taken 1 time.
1 return fail(problem, QStringLiteral("image source ") + value);
469
2/2
✓ Branch 208 → 209 taken 218 times.
✓ Branch 208 → 282 taken 332 times.
550 if (attribute == QLatin1String("style")) {
470
1/1
✓ Branch 209 → 210 taken 218 times.
218 const QString lower = value.toLower();
471
3/4
✓ Branch 211 → 212 taken 218 times.
✓ Branch 214 → 215 taken 217 times.
✓ Branch 215 → 216 taken 217 times.
✗ Branch 215 → 223 not taken.
435 if (lower.contains(QLatin1String("url(")) || lower.contains(QLatin1String("expression")) ||
472
8/10
✓ Branch 212 → 213 taken 217 times.
✓ Branch 212 → 223 taken 1 time.
✓ Branch 218 → 219 taken 217 times.
✓ Branch 219 → 220 taken 217 times.
✗ Branch 219 → 223 not taken.
✓ Branch 221 → 222 taken 217 times.
✗ Branch 222 → 223 not taken.
✓ Branch 222 → 224 taken 217 times.
✓ Branch 225 → 226 taken 1 time.
✓ Branch 225 → 235 taken 217 times.
435 lower.contains(QLatin1Char('\\')) || lower.contains(QLatin1String("@import")))
473
1/1
✓ Branch 229 → 230 taken 1 time.
1 return fail(problem, QStringLiteral("style value ") + value);
474
5/5
✓ Branch 238 → 239 taken 217 times.
✓ Branch 239 → 240 taken 217 times.
✓ Branch 240 → 241 taken 217 times.
✓ Branch 270 → 242 taken 314 times.
✓ Branch 270 → 271 taken 216 times.
530 for (const QString &declaration : value.split(QLatin1Char(';'), Qt::SkipEmptyParts)) {
475
3/3
✓ Branch 246 → 247 taken 314 times.
✓ Branch 247 → 248 taken 314 times.
✓ Branch 248 → 249 taken 314 times.
314 const QString property = declaration.section(QLatin1Char(':'), 0, 0).trimmed().toLower();
476
3/3
✓ Branch 251 → 252 taken 314 times.
✓ Branch 253 → 254 taken 1 time.
✓ Branch 253 → 263 taken 313 times.
314 if (!allowedStyleProperties().contains(property))
477
1/1
✓ Branch 257 → 258 taken 1 time.
1 return fail(problem, QStringLiteral("style property ") + property);
478 531 }
479 218 }
480 560 }
481 2992 }
482 }
483
484 1438 const QSet<QString> &MailHtmlWriter::allowedElements() {
485 static const QSet<QString> elements{
486 2 QStringLiteral("html"), QStringLiteral("head"), QStringLiteral("meta"), QStringLiteral("body"),
487 2 QStringLiteral("p"), QStringLiteral("br"), QStringLiteral("strong"), QStringLiteral("em"),
488 2 QStringLiteral("u"), QStringLiteral("s"), QStringLiteral("sup"), QStringLiteral("sub"),
489 2 QStringLiteral("code"), QStringLiteral("pre"), QStringLiteral("h2"), QStringLiteral("h3"),
490 2 QStringLiteral("h4"), QStringLiteral("blockquote"), QStringLiteral("ul"), QStringLiteral("ol"),
491 2 QStringLiteral("li"), QStringLiteral("a"), QStringLiteral("img"), QStringLiteral("span"),
492 2 QStringLiteral("hr"), QStringLiteral("table"), QStringLiteral("thead"), QStringLiteral("tbody"), QStringLiteral("tr"),
493
5/10
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 140 taken 1436 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 140 not taken.
✓ Branch 105 → 106 taken 64 times.
✓ Branch 105 → 107 taken 2 times.
✗ Branch 143 → 144 not taken.
✗ Branch 143 → 145 not taken.
✗ Branch 248 → 249 not taken.
✗ Branch 248 → 250 not taken.
1506 QStringLiteral("th"), QStringLiteral("td"), QStringLiteral("div")};
494 1438 return elements;
495 66 }
496
497 554 const QSet<QString> &MailHtmlWriter::allowedAttributes() {
498 static const QSet<QString> attributes{
499 2 QStringLiteral("style"), QStringLiteral("dir"), QStringLiteral("href"), QStringLiteral("src"),
500 2 QStringLiteral("alt"), QStringLiteral("width"), QStringLiteral("height"),
501 2 QStringLiteral("cellpadding"), QStringLiteral("cellspacing"), QStringLiteral("border"),
502 2 QStringLiteral("colspan"), QStringLiteral("rowspan"),
503
5/10
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 76 taken 552 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 76 not taken.
✓ Branch 57 → 58 taken 32 times.
✓ Branch 57 → 59 taken 2 times.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
✗ Branch 136 → 137 not taken.
✗ Branch 136 → 138 not taken.
590 QStringLiteral("charset"), QStringLiteral("name"), QStringLiteral("content"), QStringLiteral("class")};
504 554 return attributes;
505 34 }
506
507 314 const QSet<QString> &MailHtmlWriter::allowedStyleProperties() {
508 static const QSet<QString> properties{
509 2 QStringLiteral("font-family"), QStringLiteral("font-size"), QStringLiteral("color"),
510 2 QStringLiteral("background-color"), QStringLiteral("text-align"), QStringLiteral("margin"),
511 2 QStringLiteral("margin-left"), QStringLiteral("margin-right"), QStringLiteral("margin-top"),
512 2 QStringLiteral("margin-bottom"), QStringLiteral("list-style-type"),
513 2 QStringLiteral("border"), QStringLiteral("border-left"), QStringLiteral("border-collapse"),
514 2 QStringLiteral("width"), QStringLiteral("padding"), QStringLiteral("padding-left"),
515
5/10
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 84 taken 312 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 84 not taken.
✓ Branch 63 → 64 taken 36 times.
✓ Branch 63 → 65 taken 2 times.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
✗ Branch 150 → 151 not taken.
✗ Branch 150 → 152 not taken.
354 QStringLiteral("max-width")};
516 314 return properties;
517 38 }
518