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 |