Coverage Report

Created: 2026-10-14 08:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/text/qtexthtmlparser.cpp
Line
Count
Source
1
// Copyright (C) 2016 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:data-parser
4
5
#include "qtexthtmlparser_p.h"
6
7
#include <qbytearray.h>
8
#include <qstack.h>
9
#include <qdebug.h>
10
#include <qthread.h>
11
#include <qguiapplication.h>
12
13
#include "qtextdocument.h"
14
#include "qtextformat_p.h"
15
#include "qtextdocument_p.h"
16
#include "qtextcursor.h"
17
#include "qfont_p.h"
18
19
#include <algorithm>
20
21
#ifndef QT_NO_TEXTHTMLPARSER
22
23
QT_BEGIN_NAMESPACE
24
25
using namespace Qt::StringLiterals;
26
27
// see also tst_qtextdocumentfragment.cpp
28
static constexpr struct QTextHtmlEntity { QLatin1StringView name; char16_t code; } entities[]= {
29
    { "AElig"_L1, 0x00c6 },
30
    { "AMP"_L1, 38 },
31
    { "Aacute"_L1, 0x00c1 },
32
    { "Acirc"_L1, 0x00c2 },
33
    { "Agrave"_L1, 0x00c0 },
34
    { "Alpha"_L1, 0x0391 },
35
    { "Aring"_L1, 0x00c5 },
36
    { "Atilde"_L1, 0x00c3 },
37
    { "Auml"_L1, 0x00c4 },
38
    { "Beta"_L1, 0x0392 },
39
    { "Ccedil"_L1, 0x00c7 },
40
    { "Chi"_L1, 0x03a7 },
41
    { "Dagger"_L1, 0x2021 },
42
    { "Delta"_L1, 0x0394 },
43
    { "ETH"_L1, 0x00d0 },
44
    { "Eacute"_L1, 0x00c9 },
45
    { "Ecirc"_L1, 0x00ca },
46
    { "Egrave"_L1, 0x00c8 },
47
    { "Epsilon"_L1, 0x0395 },
48
    { "Eta"_L1, 0x0397 },
49
    { "Euml"_L1, 0x00cb },
50
    { "GT"_L1, 62 },
51
    { "Gamma"_L1, 0x0393 },
52
    { "Iacute"_L1, 0x00cd },
53
    { "Icirc"_L1, 0x00ce },
54
    { "Igrave"_L1, 0x00cc },
55
    { "Iota"_L1, 0x0399 },
56
    { "Iuml"_L1, 0x00cf },
57
    { "Kappa"_L1, 0x039a },
58
    { "LT"_L1, 60 },
59
    { "Lambda"_L1, 0x039b },
60
    { "Mu"_L1, 0x039c },
61
    { "Ntilde"_L1, 0x00d1 },
62
    { "Nu"_L1, 0x039d },
63
    { "OElig"_L1, 0x0152 },
64
    { "Oacute"_L1, 0x00d3 },
65
    { "Ocirc"_L1, 0x00d4 },
66
    { "Ograve"_L1, 0x00d2 },
67
    { "Omega"_L1, 0x03a9 },
68
    { "Omicron"_L1, 0x039f },
69
    { "Oslash"_L1, 0x00d8 },
70
    { "Otilde"_L1, 0x00d5 },
71
    { "Ouml"_L1, 0x00d6 },
72
    { "Phi"_L1, 0x03a6 },
73
    { "Pi"_L1, 0x03a0 },
74
    { "Prime"_L1, 0x2033 },
75
    { "Psi"_L1, 0x03a8 },
76
    { "QUOT"_L1, 34 },
77
    { "Rho"_L1, 0x03a1 },
78
    { "Scaron"_L1, 0x0160 },
79
    { "Sigma"_L1, 0x03a3 },
80
    { "THORN"_L1, 0x00de },
81
    { "Tau"_L1, 0x03a4 },
82
    { "Theta"_L1, 0x0398 },
83
    { "Uacute"_L1, 0x00da },
84
    { "Ucirc"_L1, 0x00db },
85
    { "Ugrave"_L1, 0x00d9 },
86
    { "Upsilon"_L1, 0x03a5 },
87
    { "Uuml"_L1, 0x00dc },
88
    { "Xi"_L1, 0x039e },
89
    { "Yacute"_L1, 0x00dd },
90
    { "Yuml"_L1, 0x0178 },
91
    { "Zeta"_L1, 0x0396 },
92
    { "aacute"_L1, 0x00e1 },
93
    { "acirc"_L1, 0x00e2 },
94
    { "acute"_L1, 0x00b4 },
95
    { "aelig"_L1, 0x00e6 },
96
    { "agrave"_L1, 0x00e0 },
97
    { "alefsym"_L1, 0x2135 },
98
    { "alpha"_L1, 0x03b1 },
99
    { "amp"_L1, 38 },
100
    { "and"_L1, 0x22a5 },
101
    { "ang"_L1, 0x2220 },
102
    { "apos"_L1, 0x0027 },
103
    { "aring"_L1, 0x00e5 },
104
    { "asymp"_L1, 0x2248 },
105
    { "atilde"_L1, 0x00e3 },
106
    { "auml"_L1, 0x00e4 },
107
    { "bdquo"_L1, 0x201e },
108
    { "beta"_L1, 0x03b2 },
109
    { "brvbar"_L1, 0x00a6 },
110
    { "bull"_L1, 0x2022 },
111
    { "cap"_L1, 0x2229 },
112
    { "ccedil"_L1, 0x00e7 },
113
    { "cedil"_L1, 0x00b8 },
114
    { "cent"_L1, 0x00a2 },
115
    { "chi"_L1, 0x03c7 },
116
    { "circ"_L1, 0x02c6 },
117
    { "clubs"_L1, 0x2663 },
118
    { "cong"_L1, 0x2245 },
119
    { "copy"_L1, 0x00a9 },
120
    { "crarr"_L1, 0x21b5 },
121
    { "cup"_L1, 0x222a },
122
    { "curren"_L1, 0x00a4 },
123
    { "dArr"_L1, 0x21d3 },
124
    { "dagger"_L1, 0x2020 },
125
    { "darr"_L1, 0x2193 },
126
    { "deg"_L1, 0x00b0 },
127
    { "delta"_L1, 0x03b4 },
128
    { "diams"_L1, 0x2666 },
129
    { "divide"_L1, 0x00f7 },
130
    { "eacute"_L1, 0x00e9 },
131
    { "ecirc"_L1, 0x00ea },
132
    { "egrave"_L1, 0x00e8 },
133
    { "empty"_L1, 0x2205 },
134
    { "emsp"_L1, 0x2003 },
135
    { "ensp"_L1, 0x2002 },
136
    { "epsilon"_L1, 0x03b5 },
137
    { "equiv"_L1, 0x2261 },
138
    { "eta"_L1, 0x03b7 },
139
    { "eth"_L1, 0x00f0 },
140
    { "euml"_L1, 0x00eb },
141
    { "euro"_L1, 0x20ac },
142
    { "exist"_L1, 0x2203 },
143
    { "fnof"_L1, 0x0192 },
144
    { "forall"_L1, 0x2200 },
145
    { "frac12"_L1, 0x00bd },
146
    { "frac14"_L1, 0x00bc },
147
    { "frac34"_L1, 0x00be },
148
    { "frasl"_L1, 0x2044 },
149
    { "gamma"_L1, 0x03b3 },
150
    { "ge"_L1, 0x2265 },
151
    { "gt"_L1, 62 },
152
    { "hArr"_L1, 0x21d4 },
153
    { "harr"_L1, 0x2194 },
154
    { "hearts"_L1, 0x2665 },
155
    { "hellip"_L1, 0x2026 },
156
    { "iacute"_L1, 0x00ed },
157
    { "icirc"_L1, 0x00ee },
158
    { "iexcl"_L1, 0x00a1 },
159
    { "igrave"_L1, 0x00ec },
160
    { "image"_L1, 0x2111 },
161
    { "infin"_L1, 0x221e },
162
    { "int"_L1, 0x222b },
163
    { "iota"_L1, 0x03b9 },
164
    { "iquest"_L1, 0x00bf },
165
    { "isin"_L1, 0x2208 },
166
    { "iuml"_L1, 0x00ef },
167
    { "kappa"_L1, 0x03ba },
168
    { "lArr"_L1, 0x21d0 },
169
    { "lambda"_L1, 0x03bb },
170
    { "lang"_L1, 0x2329 },
171
    { "laquo"_L1, 0x00ab },
172
    { "larr"_L1, 0x2190 },
173
    { "lceil"_L1, 0x2308 },
174
    { "ldquo"_L1, 0x201c },
175
    { "le"_L1, 0x2264 },
176
    { "lfloor"_L1, 0x230a },
177
    { "lowast"_L1, 0x2217 },
178
    { "loz"_L1, 0x25ca },
179
    { "lrm"_L1, 0x200e },
180
    { "lsaquo"_L1, 0x2039 },
181
    { "lsquo"_L1, 0x2018 },
182
    { "lt"_L1, 60 },
183
    { "macr"_L1, 0x00af },
184
    { "mdash"_L1, 0x2014 },
185
    { "micro"_L1, 0x00b5 },
186
    { "middot"_L1, 0x00b7 },
187
    { "minus"_L1, 0x2212 },
188
    { "mu"_L1, 0x03bc },
189
    { "nabla"_L1, 0x2207 },
190
    { "nbsp"_L1, 0x00a0 },
191
    { "ndash"_L1, 0x2013 },
192
    { "ne"_L1, 0x2260 },
193
    { "ni"_L1, 0x220b },
194
    { "not"_L1, 0x00ac },
195
    { "notin"_L1, 0x2209 },
196
    { "nsub"_L1, 0x2284 },
197
    { "ntilde"_L1, 0x00f1 },
198
    { "nu"_L1, 0x03bd },
199
    { "oacute"_L1, 0x00f3 },
200
    { "ocirc"_L1, 0x00f4 },
201
    { "oelig"_L1, 0x0153 },
202
    { "ograve"_L1, 0x00f2 },
203
    { "oline"_L1, 0x203e },
204
    { "omega"_L1, 0x03c9 },
205
    { "omicron"_L1, 0x03bf },
206
    { "oplus"_L1, 0x2295 },
207
    { "or"_L1, 0x22a6 },
208
    { "ordf"_L1, 0x00aa },
209
    { "ordm"_L1, 0x00ba },
210
    { "oslash"_L1, 0x00f8 },
211
    { "otilde"_L1, 0x00f5 },
212
    { "otimes"_L1, 0x2297 },
213
    { "ouml"_L1, 0x00f6 },
214
    { "para"_L1, 0x00b6 },
215
    { "part"_L1, 0x2202 },
216
    { "percnt"_L1, 0x0025 },
217
    { "permil"_L1, 0x2030 },
218
    { "perp"_L1, 0x22a5 },
219
    { "phi"_L1, 0x03c6 },
220
    { "pi"_L1, 0x03c0 },
221
    { "piv"_L1, 0x03d6 },
222
    { "plusmn"_L1, 0x00b1 },
223
    { "pound"_L1, 0x00a3 },
224
    { "prime"_L1, 0x2032 },
225
    { "prod"_L1, 0x220f },
226
    { "prop"_L1, 0x221d },
227
    { "psi"_L1, 0x03c8 },
228
    { "quot"_L1, 34 },
229
    { "rArr"_L1, 0x21d2 },
230
    { "radic"_L1, 0x221a },
231
    { "rang"_L1, 0x232a },
232
    { "raquo"_L1, 0x00bb },
233
    { "rarr"_L1, 0x2192 },
234
    { "rceil"_L1, 0x2309 },
235
    { "rdquo"_L1, 0x201d },
236
    { "real"_L1, 0x211c },
237
    { "reg"_L1, 0x00ae },
238
    { "rfloor"_L1, 0x230b },
239
    { "rho"_L1, 0x03c1 },
240
    { "rlm"_L1, 0x200f },
241
    { "rsaquo"_L1, 0x203a },
242
    { "rsquo"_L1, 0x2019 },
243
    { "sbquo"_L1, 0x201a },
244
    { "scaron"_L1, 0x0161 },
245
    { "sdot"_L1, 0x22c5 },
246
    { "sect"_L1, 0x00a7 },
247
    { "shy"_L1, 0x00ad },
248
    { "sigma"_L1, 0x03c3 },
249
    { "sigmaf"_L1, 0x03c2 },
250
    { "sim"_L1, 0x223c },
251
    { "spades"_L1, 0x2660 },
252
    { "sub"_L1, 0x2282 },
253
    { "sube"_L1, 0x2286 },
254
    { "sum"_L1, 0x2211 },
255
    { "sup"_L1, 0x2283 },
256
    { "sup1"_L1, 0x00b9 },
257
    { "sup2"_L1, 0x00b2 },
258
    { "sup3"_L1, 0x00b3 },
259
    { "supe"_L1, 0x2287 },
260
    { "szlig"_L1, 0x00df },
261
    { "tau"_L1, 0x03c4 },
262
    { "there4"_L1, 0x2234 },
263
    { "theta"_L1, 0x03b8 },
264
    { "thetasym"_L1, 0x03d1 },
265
    { "thinsp"_L1, 0x2009 },
266
    { "thorn"_L1, 0x00fe },
267
    { "tilde"_L1, 0x02dc },
268
    { "times"_L1, 0x00d7 },
269
    { "trade"_L1, 0x2122 },
270
    { "uArr"_L1, 0x21d1 },
271
    { "uacute"_L1, 0x00fa },
272
    { "uarr"_L1, 0x2191 },
273
    { "ucirc"_L1, 0x00fb },
274
    { "ugrave"_L1, 0x00f9 },
275
    { "uml"_L1, 0x00a8 },
276
    { "upsih"_L1, 0x03d2 },
277
    { "upsilon"_L1, 0x03c5 },
278
    { "uuml"_L1, 0x00fc },
279
    { "weierp"_L1, 0x2118 },
280
    { "xi"_L1, 0x03be },
281
    { "yacute"_L1, 0x00fd },
282
    { "yen"_L1, 0x00a5 },
283
    { "yuml"_L1, 0x00ff },
284
    { "zeta"_L1, 0x03b6 },
285
    { "zwj"_L1, 0x200d },
286
    { "zwnj"_L1, 0x200c }
287
};
288
289
static bool operator<(QStringView entityStr, const QTextHtmlEntity &entity)
290
0
{
291
0
    return entityStr < entity.name;
292
0
}
293
294
static bool operator<(const QTextHtmlEntity &entity, QStringView entityStr)
295
0
{
296
0
    return entity.name < entityStr;
297
0
}
298
299
static QChar resolveEntity(QStringView entity)
300
0
{
301
0
    const QTextHtmlEntity *end = std::end(entities);
302
0
    const QTextHtmlEntity *e = std::lower_bound(std::begin(entities), end, entity);
303
0
    if (e == end || (entity < *e))
304
0
        return QChar();
305
0
    return e->code;
306
0
}
307
308
static const ushort windowsLatin1ExtendedCharacters[0xA0 - 0x80] = {
309
    0x20ac, // 0x80
310
    0x0081, // 0x81 direct mapping
311
    0x201a, // 0x82
312
    0x0192, // 0x83
313
    0x201e, // 0x84
314
    0x2026, // 0x85
315
    0x2020, // 0x86
316
    0x2021, // 0x87
317
    0x02C6, // 0x88
318
    0x2030, // 0x89
319
    0x0160, // 0x8A
320
    0x2039, // 0x8B
321
    0x0152, // 0x8C
322
    0x008D, // 0x8D direct mapping
323
    0x017D, // 0x8E
324
    0x008F, // 0x8F directmapping
325
    0x0090, // 0x90 directmapping
326
    0x2018, // 0x91
327
    0x2019, // 0x92
328
    0x201C, // 0x93
329
    0X201D, // 0x94
330
    0x2022, // 0x95
331
    0x2013, // 0x96
332
    0x2014, // 0x97
333
    0x02DC, // 0x98
334
    0x2122, // 0x99
335
    0x0161, // 0x9A
336
    0x203A, // 0x9B
337
    0x0153, // 0x9C
338
    0x009D, // 0x9D direct mapping
339
    0x017E, // 0x9E
340
    0x0178  // 0x9F
341
};
342
343
// the displayMode value is according to the what are blocks in the piecetable, not
344
// what the w3c defines.
345
static constexpr QTextHtmlElement elements[]= {
346
    { "a",          Html_a,          QTextHtmlElement::DisplayInline },
347
    { "address",    Html_address,    QTextHtmlElement::DisplayInline },
348
    { "b",          Html_b,          QTextHtmlElement::DisplayInline },
349
    { "big",        Html_big,        QTextHtmlElement::DisplayInline },
350
    { "blockquote", Html_blockquote, QTextHtmlElement::DisplayBlock },
351
    { "body",       Html_body,       QTextHtmlElement::DisplayBlock },
352
    { "br",         Html_br,         QTextHtmlElement::DisplayInline },
353
    { "caption",    Html_caption,    QTextHtmlElement::DisplayBlock },
354
    { "center",     Html_center,     QTextHtmlElement::DisplayBlock },
355
    { "cite",       Html_cite,       QTextHtmlElement::DisplayInline },
356
    { "code",       Html_code,       QTextHtmlElement::DisplayInline },
357
    { "dd",         Html_dd,         QTextHtmlElement::DisplayBlock },
358
    { "del",        Html_del,        QTextHtmlElement::DisplayInline },
359
    { "dfn",        Html_dfn,        QTextHtmlElement::DisplayInline },
360
    { "div",        Html_div,        QTextHtmlElement::DisplayBlock },
361
    { "dl",         Html_dl,         QTextHtmlElement::DisplayBlock },
362
    { "dt",         Html_dt,         QTextHtmlElement::DisplayBlock },
363
    { "em",         Html_em,         QTextHtmlElement::DisplayInline },
364
    { "font",       Html_font,       QTextHtmlElement::DisplayInline },
365
    { "h1",         Html_h1,         QTextHtmlElement::DisplayBlock },
366
    { "h2",         Html_h2,         QTextHtmlElement::DisplayBlock },
367
    { "h3",         Html_h3,         QTextHtmlElement::DisplayBlock },
368
    { "h4",         Html_h4,         QTextHtmlElement::DisplayBlock },
369
    { "h5",         Html_h5,         QTextHtmlElement::DisplayBlock },
370
    { "h6",         Html_h6,         QTextHtmlElement::DisplayBlock },
371
    { "head",       Html_head,       QTextHtmlElement::DisplayNone },
372
    { "hr",         Html_hr,         QTextHtmlElement::DisplayBlock },
373
    { "html",       Html_html,       QTextHtmlElement::DisplayInline },
374
    { "i",          Html_i,          QTextHtmlElement::DisplayInline },
375
    { "img",        Html_img,        QTextHtmlElement::DisplayInline },
376
    { "kbd",        Html_kbd,        QTextHtmlElement::DisplayInline },
377
    { "li",         Html_li,         QTextHtmlElement::DisplayBlock },
378
    { "link",       Html_link,       QTextHtmlElement::DisplayNone },
379
    { "meta",       Html_meta,       QTextHtmlElement::DisplayNone },
380
    { "nobr",       Html_nobr,       QTextHtmlElement::DisplayInline },
381
    { "ol",         Html_ol,         QTextHtmlElement::DisplayBlock },
382
    { "p",          Html_p,          QTextHtmlElement::DisplayBlock },
383
    { "pre",        Html_pre,        QTextHtmlElement::DisplayBlock },
384
    { "qt",         Html_body /*deliberate mapping*/, QTextHtmlElement::DisplayBlock },
385
    { "s",          Html_s,          QTextHtmlElement::DisplayInline },
386
    { "samp",       Html_samp,       QTextHtmlElement::DisplayInline },
387
    { "script",     Html_script,     QTextHtmlElement::DisplayNone },
388
    { "small",      Html_small,      QTextHtmlElement::DisplayInline },
389
    { "span",       Html_span,       QTextHtmlElement::DisplayInline },
390
    { "strong",     Html_strong,     QTextHtmlElement::DisplayInline },
391
    { "style",      Html_style,      QTextHtmlElement::DisplayNone },
392
    { "sub",        Html_sub,        QTextHtmlElement::DisplayInline },
393
    { "sup",        Html_sup,        QTextHtmlElement::DisplayInline },
394
    { "table",      Html_table,      QTextHtmlElement::DisplayTable },
395
    { "tbody",      Html_tbody,      QTextHtmlElement::DisplayTable },
396
    { "td",         Html_td,         QTextHtmlElement::DisplayBlock },
397
    { "tfoot",      Html_tfoot,      QTextHtmlElement::DisplayTable },
398
    { "th",         Html_th,         QTextHtmlElement::DisplayBlock },
399
    { "thead",      Html_thead,      QTextHtmlElement::DisplayTable },
400
    { "title",      Html_title,      QTextHtmlElement::DisplayNone },
401
    { "tr",         Html_tr,         QTextHtmlElement::DisplayTable },
402
    { "tt",         Html_tt,         QTextHtmlElement::DisplayInline },
403
    { "u",          Html_u,          QTextHtmlElement::DisplayInline },
404
    { "ul",         Html_ul,         QTextHtmlElement::DisplayBlock },
405
    { "var",        Html_var,        QTextHtmlElement::DisplayInline },
406
};
407
408
template <typename T>
409
static bool operator<(T str, QTextHtmlElement e)
410
0
{
411
0
    return str.compare(QLatin1StringView(e.name), Qt::CaseInsensitive) < 0;
412
0
}
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QStringView>(QStringView, QTextHtmlElement)
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QLatin1String>(QLatin1String, QTextHtmlElement)
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QBasicUtf8StringView<false> >(QBasicUtf8StringView<false>, QTextHtmlElement)
413
414
template <typename T>
415
static bool operator<(QTextHtmlElement e, T str)
416
0
{
417
0
    return QLatin1StringView(e.name).compare(str, Qt::CaseInsensitive) < 0;
418
0
}
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QStringView>(QTextHtmlElement, QStringView)
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QLatin1String>(QTextHtmlElement, QLatin1String)
Unexecuted instantiation: qtexthtmlparser.cpp:bool operator< <QBasicUtf8StringView<false> >(QTextHtmlElement, QBasicUtf8StringView<false>)
419
420
static const QTextHtmlElement *lookupElementHelper(QAnyStringView element)
421
0
{
422
0
    return element.visit([begin = std::begin(elements), end = std::end(elements)]
423
0
                         (auto element) -> const QTextHtmlElement * {
424
0
        const auto e = std::lower_bound(begin, end, element);
425
0
        if ((e == end) || (element < *e))
426
0
            return nullptr;
427
0
        return e;
428
0
    });
Unexecuted instantiation: qtexthtmlparser.cpp:QTextHtmlElement const* lookupElementHelper(QAnyStringView)::$_0::operator()<QStringView>(QStringView) const
Unexecuted instantiation: qtexthtmlparser.cpp:QTextHtmlElement const* lookupElementHelper(QAnyStringView)::$_0::operator()<QLatin1String>(QLatin1String) const
Unexecuted instantiation: qtexthtmlparser.cpp:QTextHtmlElement const* lookupElementHelper(QAnyStringView)::$_0::operator()<QBasicUtf8StringView<false> >(QBasicUtf8StringView<false>) const
429
0
}
430
431
int QTextHtmlParser::lookupElement(QAnyStringView element)
432
0
{
433
0
    const QTextHtmlElement *e = lookupElementHelper(element);
434
0
    if (!e)
435
0
        return -1;
436
0
    return e->id;
437
0
}
438
439
// quotes newlines as "\\n"
440
static QString quoteNewline(const QString &s)
441
0
{
442
0
    QString n = s;
443
0
    n.replace(u'\n', "\\n"_L1);
444
0
    return n;
445
0
}
446
447
QTextHtmlParserNode::QTextHtmlParserNode()
448
0
    : parent(0), id(Html_unknown),
449
0
      cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false), hasOwnLineHeightType(false), hasLineHeightMultiplier(false),
450
0
      hasCssListIndent(false), isEmptyParagraph(false), isTextFrame(false), isRootFrame(false),
451
0
      displayMode(QTextHtmlElement::DisplayInline), hasHref(false),
452
0
      listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
453
0
      tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0),
454
0
      borderBrush(Qt::darkGray), borderStyle(QTextFrameFormat::BorderStyle_Outset),
455
0
      borderCollapse(false),
456
0
      userState(-1), cssListIndent(0), wsm(WhiteSpaceModeUndefined)
457
0
{
458
0
    margin[QTextHtmlParser::MarginLeft] = 0;
459
0
    margin[QTextHtmlParser::MarginRight] = 0;
460
0
    margin[QTextHtmlParser::MarginTop] = 0;
461
0
    margin[QTextHtmlParser::MarginBottom] = 0;
462
463
0
    for (int i = 0; i < 4; ++i) {
464
0
        tableCellBorderStyle[i] = QTextFrameFormat::BorderStyle_None;
465
0
        tableCellBorder[i] = 0;
466
0
        tableCellBorderBrush[i] = Qt::NoBrush;
467
0
    }
468
0
}
469
470
void QTextHtmlParser::dumpHtml()
471
0
{
472
0
    for (int i = 0; i < count(); ++i) {
473
0
        qDebug().nospace() << qPrintable(QString(depth(i) * 4, u' '))
474
0
                           << qPrintable(at(i).tag) << ':'
475
0
                           << quoteNewline(at(i).text);
476
0
    }
477
0
}
478
479
QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
480
0
{
481
0
    QTextHtmlParserNode *lastNode = nodes.last();
482
0
    QTextHtmlParserNode *newNode = nullptr;
483
484
0
    bool reuseLastNode = true;
485
486
0
    if (nodes.size() == 1) {
487
0
        reuseLastNode = false;
488
0
    } else if (lastNode->tag.isEmpty()) {
489
490
0
        if (lastNode->text.isEmpty()) {
491
0
            reuseLastNode = true;
492
0
        } else { // last node is a text node (empty tag) with some text
493
494
0
            if (lastNode->text.size() == 1 && lastNode->text.at(0).isSpace()) {
495
496
0
                int lastSibling = count() - 2;
497
0
                while (lastSibling
498
0
                       && at(lastSibling).parent != lastNode->parent
499
0
                       && at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
500
0
                    lastSibling = at(lastSibling).parent;
501
0
                }
502
503
0
                if (at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
504
0
                    reuseLastNode = false;
505
0
                } else {
506
0
                    reuseLastNode = true;
507
0
                }
508
0
            } else {
509
                // text node with real (non-whitespace) text -> nothing to re-use
510
0
                reuseLastNode = false;
511
0
            }
512
513
0
        }
514
515
0
    } else {
516
        // last node had a proper tag -> nothing to re-use
517
0
        reuseLastNode = false;
518
0
    }
519
520
0
    if (reuseLastNode) {
521
0
        newNode = lastNode;
522
0
        newNode->tag.clear();
523
0
        newNode->text.clear();
524
0
        newNode->id = Html_unknown;
525
0
    } else {
526
0
        nodes.append(new QTextHtmlParserNode);
527
0
        newNode = nodes.last();
528
0
    }
529
530
0
    newNode->parent = parent;
531
0
    return newNode;
532
0
}
533
534
void QTextHtmlParser::parse(const QString &text, const QTextDocument *_resourceProvider)
535
0
{
536
0
    qDeleteAll(nodes);
537
0
    nodes.clear();
538
0
    nodes.append(new QTextHtmlParserNode);
539
0
    txt = text;
540
0
    pos = 0;
541
0
    len = txt.size();
542
0
    textEditMode = false;
543
0
    resourceProvider = _resourceProvider;
544
0
    parse();
545
    //dumpHtml();
546
0
}
547
548
int QTextHtmlParser::depth(int i) const
549
0
{
550
0
    int depth = 0;
551
0
    while (i) {
552
0
        i = at(i).parent;
553
0
        ++depth;
554
0
    }
555
0
    return depth;
556
0
}
557
558
0
int QTextHtmlParser::margin(int i, int mar) const {
559
0
    int m = 0;
560
0
    const QTextHtmlParserNode *node;
561
0
    if (mar == MarginLeft
562
0
        || mar == MarginRight) {
563
0
        while (i) {
564
0
            node = &at(i);
565
0
            if (!node->isBlock() && node->id != Html_table)
566
0
                break;
567
0
            if (node->isTableCell())
568
0
                break;
569
0
            m += node->margin[mar];
570
0
            i = node->parent;
571
0
        }
572
0
    }
573
0
    return m;
574
0
}
575
576
int QTextHtmlParser::topMargin(int i) const
577
0
{
578
0
    if (!i)
579
0
        return 0;
580
0
    return at(i).margin[MarginTop];
581
0
}
582
583
int QTextHtmlParser::bottomMargin(int i) const
584
0
{
585
0
    if (!i)
586
0
        return 0;
587
0
    return at(i).margin[MarginBottom];
588
0
}
589
590
void QTextHtmlParser::eatSpace()
591
0
{
592
0
    while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
593
0
        pos++;
594
0
}
595
596
void QTextHtmlParser::parse()
597
0
{
598
0
    while (pos < len) {
599
0
        QChar c = txt.at(pos++);
600
0
        if (c == u'<') {
601
0
            parseTag();
602
0
        } else if (c == u'&') {
603
0
            nodes.last()->text += parseEntity();
604
0
        } else {
605
0
            nodes.last()->text += c;
606
0
        }
607
0
    }
608
0
}
609
610
// parses a tag after "<"
611
void QTextHtmlParser::parseTag()
612
0
{
613
0
    eatSpace();
614
615
    // handle comments and other exclamation mark declarations
616
0
    if (hasPrefix(u'!')) {
617
0
        parseExclamationTag();
618
0
        if (nodes.last()->wsm != QTextHtmlParserNode::WhiteSpacePre
619
0
            && nodes.last()->wsm != QTextHtmlParserNode::WhiteSpacePreWrap
620
0
                && !textEditMode)
621
0
            eatSpace();
622
0
        return;
623
0
    }
624
625
    // if close tag just close
626
0
    if (hasPrefix(u'/')) {
627
0
        if (nodes.last()->id == Html_style) {
628
0
#ifndef QT_NO_CSSPARSER
629
0
            QCss::Parser parser(nodes.constLast()->text);
630
0
            QCss::StyleSheet sheet;
631
0
            sheet.origin = QCss::StyleSheetOrigin_Author;
632
0
            parser.parse(&sheet, Qt::CaseInsensitive);
633
0
            inlineStyleSheets.append(sheet);
634
0
            resolveStyleSheetImports(sheet);
635
0
#endif
636
0
        }
637
0
        parseCloseTag();
638
0
        return;
639
0
    }
640
641
0
    int p = last();
642
0
    while (p && at(p).tag.size() == 0)
643
0
        p = at(p).parent;
644
645
0
    QTextHtmlParserNode *node = newNode(p);
646
647
    // parse tag name
648
0
    node->tag = parseWord().toLower();
649
650
0
    const QTextHtmlElement *elem = lookupElementHelper(node->tag);
651
0
    if (elem) {
652
0
        node->id = elem->id;
653
0
        node->displayMode = elem->displayMode;
654
0
    } else {
655
0
        node->id = Html_unknown;
656
0
    }
657
658
0
    node->attributes.clear();
659
    // _need_ at least one space after the tag name, otherwise there can't be attributes
660
0
    if (pos < len && txt.at(pos).isSpace())
661
0
        node->attributes = parseAttributes();
662
663
    // resolveParent() may have to change the order in the tree and
664
    // insert intermediate nodes for buggy HTML, so re-initialize the 'node'
665
    // pointer through the return value
666
0
    node = resolveParent();
667
0
    resolveNode();
668
669
0
#ifndef QT_NO_CSSPARSER
670
0
    const int nodeIndex = nodes.size() - 1; // this new node is always the last
671
0
    node->applyCssDeclarations(declarationsForNode(nodeIndex), resourceProvider);
672
0
#endif
673
0
    applyAttributes(node->attributes);
674
675
    // finish tag
676
0
    bool tagClosed = false;
677
0
    while (pos < len && txt.at(pos) != u'>') {
678
0
        if (txt.at(pos) == u'/')
679
0
            tagClosed = true;
680
681
0
        pos++;
682
0
    }
683
0
    pos++;
684
685
    // in a white-space preserving environment strip off a initial newline
686
    // since the element itself already generates a newline
687
0
    if ((node->wsm == QTextHtmlParserNode::WhiteSpacePre
688
0
         || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap
689
0
         || node->wsm == QTextHtmlParserNode::WhiteSpacePreLine)
690
0
        && node->isBlock()) {
691
0
        if (pos < len - 1 && txt.at(pos) == u'\n')
692
0
            ++pos;
693
0
    }
694
695
0
    if (node->mayNotHaveChildren() || tagClosed) {
696
0
        newNode(node->parent);
697
0
        resolveNode();
698
0
    }
699
0
}
700
701
// parses a tag beginning with "/"
702
void QTextHtmlParser::parseCloseTag()
703
0
{
704
0
    ++pos;
705
0
    QString tag = parseWord().toLower().trimmed();
706
0
    while (pos < len) {
707
0
        QChar c = txt.at(pos++);
708
0
        if (c == u'>')
709
0
            break;
710
0
    }
711
712
    // find corresponding open node
713
0
    int p = last();
714
0
    if (p > 0
715
0
        && at(p - 1).tag == tag
716
0
        && at(p - 1).mayNotHaveChildren())
717
0
        p--;
718
719
0
    while (p && at(p).tag != tag)
720
0
        p = at(p).parent;
721
722
    // simply ignore the tag if we can't find
723
    // a corresponding open node, for broken
724
    // html such as <font>blah</font></font>
725
0
    if (!p)
726
0
        return;
727
728
    // in a white-space preserving environment strip off a trailing newline
729
    // since the closing of the opening block element will automatically result
730
    // in a new block for elements following the <pre>
731
    // ...foo\n</pre><p>blah -> foo</pre><p>blah
732
0
    if ((at(p).wsm == QTextHtmlParserNode::WhiteSpacePre
733
0
         || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap
734
0
         || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreLine)
735
0
        && at(p).isBlock()) {
736
0
        if (at(last()).text.endsWith(u'\n'))
737
0
            nodes[last()]->text.chop(1);
738
0
    }
739
740
0
    newNode(at(p).parent);
741
0
    resolveNode();
742
0
}
743
744
// parses a tag beginning with "!"
745
void QTextHtmlParser::parseExclamationTag()
746
0
{
747
0
    ++pos;
748
0
    if (hasPrefix(u'-') && hasPrefix(u'-', 1)) {
749
0
        pos += 2;
750
        // eat comments
751
0
        int end = txt.indexOf("-->"_L1, pos);
752
0
        pos = (end >= 0 ? end + 3 : len);
753
0
    } else {
754
        // eat internal tags
755
0
        while (pos < len) {
756
0
            QChar c = txt.at(pos++);
757
0
            if (c == u'>')
758
0
                break;
759
0
        }
760
0
    }
761
0
}
762
763
QString QTextHtmlParser::parseEntity(QStringView entity)
764
0
{
765
0
    QChar resolved = resolveEntity(entity);
766
0
    if (!resolved.isNull())
767
0
        return QString(resolved);
768
769
0
    if (entity.size() > 1 && entity.at(0) == u'#') {
770
0
        entity = entity.mid(1); // removing leading #
771
772
0
        int base = 10;
773
0
        bool ok = false;
774
775
0
        if (entity.at(0).toLower() == u'x') { // hex entity?
776
0
            entity = entity.mid(1);
777
0
            base = 16;
778
0
        }
779
780
0
        uint uc = entity.toUInt(&ok, base);
781
0
        if (ok) {
782
0
            if (uc >= 0x80 && uc < 0x80 + (sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0])))
783
0
                uc = windowsLatin1ExtendedCharacters[uc - 0x80];
784
0
            return QStringView{QChar::fromUcs4(uc)}.toString();
785
0
        }
786
0
    }
787
0
    return {};
788
0
}
789
790
// parses an entity after "&", and returns it
791
QString QTextHtmlParser::parseEntity()
792
0
{
793
0
    const int recover = pos;
794
0
    int entityLen = 0;
795
0
    while (pos < len) {
796
0
        QChar c = txt.at(pos++);
797
0
        if (c.isSpace() || pos - recover > 9) {
798
0
            goto error;
799
0
        }
800
0
        if (c == u';')
801
0
            break;
802
0
        ++entityLen;
803
0
    }
804
0
    if (entityLen) {
805
0
        const QStringView entity = QStringView(txt).mid(recover, entityLen);
806
0
        QString parsedEntity = parseEntity(entity);
807
0
        if (!parsedEntity.isNull()) {
808
0
            return parsedEntity;
809
0
        }
810
0
    }
811
0
error:
812
0
    pos = recover;
813
0
    return "&"_L1;
814
0
}
815
816
// parses one word, possibly quoted, and returns it
817
QString QTextHtmlParser::parseWord()
818
0
{
819
0
    QString word;
820
0
    if (hasPrefix(u'\"')) { // double quotes
821
0
        ++pos;
822
0
        while (pos < len) {
823
0
            QChar c = txt.at(pos++);
824
0
            if (c == u'\"')
825
0
                break;
826
0
            else if (c == u'&')
827
0
                word += parseEntity();
828
0
            else
829
0
                word += c;
830
0
        }
831
0
    } else if (hasPrefix(u'\'')) { // single quotes
832
0
        ++pos;
833
0
        while (pos < len) {
834
0
            QChar c = txt.at(pos++);
835
            // Allow for escaped single quotes as they may be part of the string
836
0
            if (c == u'\'' && (txt.size() > 1 && txt.at(pos - 2) != u'\\'))
837
0
                break;
838
0
            else
839
0
                word += c;
840
0
        }
841
0
    } else { // normal text
842
0
        while (pos < len) {
843
0
            QChar c = txt.at(pos++);
844
0
            if (c == u'>' || (c == u'/' && hasPrefix(u'>'))
845
0
                    || c == u'<' || c == u'=' || c.isSpace()) {
846
0
                --pos;
847
0
                break;
848
0
            }
849
0
            if (c == u'&')
850
0
                word += parseEntity();
851
0
            else
852
0
                word += c;
853
0
        }
854
0
    }
855
0
    return word;
856
0
}
857
858
// gives the new node the right parent
859
QTextHtmlParserNode *QTextHtmlParser::resolveParent()
860
0
{
861
0
    QTextHtmlParserNode *node = nodes.last();
862
863
0
    int p = node->parent;
864
865
    // Excel gives us buggy HTML with just tr without surrounding table tags
866
    // or with just td tags
867
868
0
    if (node->id == Html_td) {
869
0
        int n = p;
870
0
        while (n && at(n).id != Html_tr)
871
0
            n = at(n).parent;
872
873
0
        if (!n) {
874
0
            nodes.insert(nodes.size() - 1, new QTextHtmlParserNode);
875
0
            nodes.insert(nodes.size() - 1, new QTextHtmlParserNode);
876
877
0
            QTextHtmlParserNode *table = nodes[nodes.size() - 3];
878
0
            table->parent = p;
879
0
            table->id = Html_table;
880
0
            table->tag = "table"_L1;
881
0
            table->children.append(nodes.size() - 2); // add row as child
882
883
0
            QTextHtmlParserNode *row = nodes[nodes.size() - 2];
884
0
            row->parent = nodes.size() - 3; // table as parent
885
0
            row->id = Html_tr;
886
0
            row->tag = "tr"_L1;
887
888
0
            p = nodes.size() - 2;
889
0
            node = nodes.last(); // re-initialize pointer
890
0
        }
891
0
    }
892
893
0
    if (node->id == Html_tr) {
894
0
        int n = p;
895
0
        while (n && at(n).id != Html_table)
896
0
            n = at(n).parent;
897
898
0
        if (!n) {
899
0
            nodes.insert(nodes.size() - 1, new QTextHtmlParserNode);
900
0
            QTextHtmlParserNode *table = nodes[nodes.size() - 2];
901
0
            table->parent = p;
902
0
            table->id = Html_table;
903
0
            table->tag = "table"_L1;
904
0
            p = nodes.size() - 2;
905
0
            node = nodes.last(); // re-initialize pointer
906
0
        }
907
0
    }
908
909
    // permit invalid html by letting block elements be children
910
    // of inline elements with the exception of paragraphs:
911
    //
912
    // a new paragraph closes parent inline elements (while loop),
913
    // unless they themselves are children of a non-paragraph block
914
    // element (if statement)
915
    //
916
    // For example:
917
    //
918
    // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
919
    //                           belongs to the first <p>. The self-nesting
920
    //                           check further down prevents the second <p>
921
    //                           from nesting into the first one then.
922
    //                           so Bar is not bold.
923
    //
924
    // <body><b><p>Foo <-- Foo should be bold.
925
    //
926
    // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
927
    //
928
0
    if (node->id == Html_p) {
929
0
        while (p && !at(p).isBlock())
930
0
            p = at(p).parent;
931
932
0
        if (!p || at(p).id != Html_p)
933
0
            p = node->parent;
934
0
    }
935
936
    // some elements are not self nesting
937
0
    if (node->id == at(p).id
938
0
        && node->isNotSelfNesting())
939
0
        p = at(p).parent;
940
941
    // some elements are not allowed in certain contexts
942
0
    while ((p && !node->allowedInContext(at(p).id))
943
           // ### make new styles aware of empty tags
944
0
           || at(p).mayNotHaveChildren()
945
0
       ) {
946
0
        p = at(p).parent;
947
0
    }
948
949
0
    node->parent = p;
950
951
    // makes it easier to traverse the tree, later
952
0
    nodes[p]->children.append(nodes.size() - 1);
953
0
    return node;
954
0
}
955
956
// sets all properties on the new node
957
void QTextHtmlParser::resolveNode()
958
0
{
959
0
    QTextHtmlParserNode *node = nodes.last();
960
0
    const QTextHtmlParserNode *parent = nodes.at(node->parent);
961
0
    node->initializeProperties(parent, this);
962
0
}
963
964
bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
965
0
{
966
0
    if (!isListStart())
967
0
        return false;
968
969
0
    int p = parent;
970
0
    while (p) {
971
0
        if (parser->at(p).isListStart())
972
0
            return true;
973
0
        p = parser->at(p).parent;
974
0
    }
975
0
    return false;
976
0
}
977
978
void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
979
0
{
980
    // inherit properties from parent element
981
0
    charFormat = parent->charFormat;
982
983
0
    if (id == Html_html)
984
0
        blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
985
0
    else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
986
0
        blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
987
988
0
    if (parent->displayMode == QTextHtmlElement::DisplayNone)
989
0
        displayMode = QTextHtmlElement::DisplayNone;
990
991
0
    if (parent->id != Html_table || id == Html_caption) {
992
0
        if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
993
0
            blockFormat.setAlignment(parent->blockFormat.alignment());
994
0
        else
995
0
            blockFormat.clearProperty(QTextFormat::BlockAlignment);
996
0
    }
997
    // we don't paint per-row background colors, yet. so as an
998
    // exception inherit the background color here
999
    // we also inherit the background between inline elements
1000
    // we also inherit from non-body block elements since we merge them together
1001
0
    if ((parent->id != Html_tr || !isTableCell())
1002
0
        && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)
1003
0
        && (parent->id == Html_body || displayMode != QTextHtmlElement::DisplayBlock || parent->displayMode != QTextHtmlElement::DisplayBlock)
1004
0
       ) {
1005
0
        charFormat.clearProperty(QTextFormat::BackgroundBrush);
1006
0
    }
1007
1008
0
    listStyle = parent->listStyle;
1009
    // makes no sense to inherit that property, a named anchor is a single point
1010
    // in the document, which is set by the DocumentFragment
1011
0
    charFormat.clearProperty(QTextFormat::AnchorName);
1012
0
    wsm = parent->wsm;
1013
1014
    // initialize remaining properties
1015
0
    margin[QTextHtmlParser::MarginLeft] = 0;
1016
0
    margin[QTextHtmlParser::MarginRight] = 0;
1017
0
    margin[QTextHtmlParser::MarginTop] = 0;
1018
0
    margin[QTextHtmlParser::MarginBottom] = 0;
1019
0
    cssFloat = QTextFrameFormat::InFlow;
1020
1021
0
    for (int i = 0; i < 4; ++i)
1022
0
        padding[i] = -1;
1023
1024
    // set element specific attributes
1025
0
    switch (id) {
1026
0
        case Html_a:
1027
0
            for (int i = 0; i < attributes.size(); i += 2) {
1028
0
                const QString key = attributes.at(i);
1029
0
                if (key.compare("href"_L1, Qt::CaseInsensitive) == 0
1030
0
                    && !attributes.at(i + 1).isEmpty()) {
1031
0
                    hasHref = true;
1032
0
                }
1033
0
            }
1034
0
            charFormat.setAnchor(true);
1035
0
            break;
1036
0
        case Html_big:
1037
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1038
0
            break;
1039
0
        case Html_small:
1040
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1041
0
            break;
1042
0
        case Html_h1:
1043
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
1044
0
            margin[QTextHtmlParser::MarginTop] = 18;
1045
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1046
0
            break;
1047
0
        case Html_h2:
1048
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
1049
0
            margin[QTextHtmlParser::MarginTop] = 16;
1050
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1051
0
            break;
1052
0
        case Html_h3:
1053
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1054
0
            margin[QTextHtmlParser::MarginTop] = 14;
1055
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1056
0
            break;
1057
0
        case Html_h4:
1058
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
1059
0
            margin[QTextHtmlParser::MarginTop] = 12;
1060
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1061
0
            break;
1062
0
        case Html_h5:
1063
0
            charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1064
0
            margin[QTextHtmlParser::MarginTop] = 12;
1065
0
            margin[QTextHtmlParser::MarginBottom] = 4;
1066
0
            break;
1067
0
        case Html_p:
1068
0
            margin[QTextHtmlParser::MarginTop] = 12;
1069
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1070
0
            break;
1071
0
        case Html_ul:
1072
            // nested lists don't have margins, except for the toplevel one
1073
0
            if (!isNestedList(parser)) {
1074
0
                margin[QTextHtmlParser::MarginTop] = 12;
1075
0
                margin[QTextHtmlParser::MarginBottom] = 12;
1076
0
            }
1077
            // no left margin as we use indenting instead
1078
0
            break;
1079
0
        case Html_ol:
1080
            // nested lists don't have margins, except for the toplevel one
1081
0
            if (!isNestedList(parser)) {
1082
0
                margin[QTextHtmlParser::MarginTop] = 12;
1083
0
                margin[QTextHtmlParser::MarginBottom] = 12;
1084
0
            }
1085
            // no left margin as we use indenting instead
1086
0
            break;
1087
0
        case Html_br:
1088
0
            text = QChar(QChar::LineSeparator);
1089
0
            break;
1090
0
        case Html_pre:
1091
0
            margin[QTextHtmlParser::MarginTop] = 12;
1092
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1093
0
            break;
1094
0
        case Html_blockquote:
1095
0
            margin[QTextHtmlParser::MarginTop] = 12;
1096
0
            margin[QTextHtmlParser::MarginBottom] = 12;
1097
0
            margin[QTextHtmlParser::MarginLeft] = 40;
1098
0
            margin[QTextHtmlParser::MarginRight] = 40;
1099
0
            blockFormat.setProperty(QTextFormat::BlockQuoteLevel, 1);
1100
0
            break;
1101
0
        case Html_dl:
1102
0
            margin[QTextHtmlParser::MarginTop] = 8;
1103
0
            margin[QTextHtmlParser::MarginBottom] = 8;
1104
0
            break;
1105
0
        case Html_dd:
1106
0
            margin[QTextHtmlParser::MarginLeft] = 30;
1107
0
            break;
1108
0
        default: break;
1109
0
    }
1110
0
}
1111
1112
#ifndef QT_NO_CSSPARSER
1113
void QTextHtmlParserNode::setListStyle(const QList<QCss::Value> &cssValues)
1114
0
{
1115
0
    for (int i = 0; i < cssValues.size(); ++i) {
1116
0
        if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
1117
0
            switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
1118
0
                case QCss::Value_None: hasOwnListStyle = true; listStyle = QTextListFormat::ListStyleUndefined; break;
1119
0
                case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
1120
0
                case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
1121
0
                case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
1122
0
                case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
1123
0
                case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
1124
0
                case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
1125
0
                case QCss::Value_LowerRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerRoman; break;
1126
0
                case QCss::Value_UpperRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperRoman; break;
1127
0
                default: break;
1128
0
            }
1129
0
        }
1130
0
    }
1131
    // allow individual list items to override the style
1132
0
    if (id == Html_li && hasOwnListStyle)
1133
0
        blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
1134
0
}
1135
1136
static QTextFrameFormat::BorderStyle toQTextFrameFormat(QCss::BorderStyle cssStyle)
1137
0
{
1138
0
    switch (cssStyle) {
1139
0
    case QCss::BorderStyle::BorderStyle_Dotted:
1140
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Dotted;
1141
0
    case QCss::BorderStyle::BorderStyle_Dashed:
1142
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Dashed;
1143
0
    case QCss::BorderStyle::BorderStyle_Solid:
1144
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Solid;
1145
0
    case QCss::BorderStyle::BorderStyle_Double:
1146
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Double;
1147
0
    case QCss::BorderStyle::BorderStyle_DotDash:
1148
0
        return QTextFrameFormat::BorderStyle::BorderStyle_DotDash;
1149
0
    case QCss::BorderStyle::BorderStyle_DotDotDash:
1150
0
        return QTextFrameFormat::BorderStyle::BorderStyle_DotDotDash;
1151
0
    case QCss::BorderStyle::BorderStyle_Groove:
1152
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Groove;
1153
0
    case QCss::BorderStyle::BorderStyle_Ridge:
1154
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Ridge;
1155
0
    case QCss::BorderStyle::BorderStyle_Inset:
1156
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Inset;
1157
0
    case QCss::BorderStyle::BorderStyle_Outset:
1158
0
        return QTextFrameFormat::BorderStyle::BorderStyle_Outset;
1159
0
    case QCss::BorderStyle::BorderStyle_Unknown:
1160
0
    case QCss::BorderStyle::BorderStyle_None:
1161
0
    case QCss::BorderStyle::BorderStyle_Native:
1162
0
        return QTextFrameFormat::BorderStyle::BorderStyle_None;
1163
    // Intentionally no "default" to allow a compiler warning when extending the enum
1164
    // without updating this here. clang gives such a warning.
1165
0
    }
1166
    // Must not happen, intentionally trigger undefined behavior which sanitizers will detect.
1167
    // Having all cases covered in switch is not sufficient:
1168
    // MSVC would warn when there is no "default".
1169
0
    return static_cast<QTextFrameFormat::BorderStyle>(-1);
1170
0
}
1171
1172
void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
1173
0
{
1174
0
    QCss::ValueExtractor extractor(declarations);
1175
0
    extractor.extractBox(margin, padding);
1176
1177
0
    auto getBorderValues = [&extractor](qreal *borderWidth, QBrush *borderBrush, QTextFrameFormat::BorderStyle *borderStyles) {
1178
0
        QCss::BorderStyle cssStyles[4];
1179
0
        int cssBorder[4];
1180
0
        QSize cssRadii[4]; // unused
1181
0
        for (int i = 0; i < 4; ++i) {
1182
0
            cssStyles[i] = QCss::BorderStyle_None;
1183
0
            cssBorder[i] = 0;
1184
0
        }
1185
        // this will parse (and cache) "border-width" as a list so the
1186
        // QCss::BorderWidth parsing below which expects a single value
1187
        // will not work as expected - which in this case does not matter
1188
        // because tableBorder is not relevant for cells.
1189
0
        bool hit = extractor.extractBorder(cssBorder, borderBrush, cssStyles, cssRadii);
1190
0
        for (int i = 0; i < 4; ++i) {
1191
0
            borderStyles[i] = toQTextFrameFormat(cssStyles[i]);
1192
0
            borderWidth[i] = static_cast<qreal>(cssBorder[i]);
1193
0
        }
1194
0
        return hit;
1195
0
    };
1196
1197
0
    if (id == Html_td || id == Html_th)
1198
0
        getBorderValues(tableCellBorder, tableCellBorderBrush, tableCellBorderStyle);
1199
1200
0
    for (int i = 0; i < declarations.size(); ++i) {
1201
0
        const QCss::Declaration &decl = declarations.at(i);
1202
0
        if (decl.d->values.isEmpty()) continue;
1203
1204
0
        QCss::KnownValue identifier = QCss::UnknownValue;
1205
0
        if (decl.d->values.constFirst().type == QCss::Value::KnownIdentifier)
1206
0
            identifier = static_cast<QCss::KnownValue>(decl.d->values.constFirst().variant.toInt());
1207
1208
0
        switch (decl.d->propertyId) {
1209
0
        case QCss::BorderColor: {
1210
0
            QBrush bordersBrush[4];
1211
0
            decl.brushValues(bordersBrush);
1212
0
            if (bordersBrush[0].color().isValid())
1213
0
                borderBrush = bordersBrush[0];
1214
0
            break;
1215
0
        }
1216
0
        case QCss::BorderStyles:
1217
0
            if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
1218
0
                borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
1219
0
            break;
1220
0
        case QCss::BorderWidth: {
1221
0
            int borders[4];
1222
0
            extractor.lengthValues(decl, borders);
1223
0
            tableBorder = borders[0];
1224
0
            }
1225
0
            break;
1226
0
        case QCss::Border: {
1227
0
            qreal tblBorder[4];
1228
0
            QBrush tblBorderBrush[4];
1229
0
            QTextFrameFormat::BorderStyle tblBorderStyle[4];
1230
0
            if (getBorderValues(tblBorder, tblBorderBrush, tblBorderStyle)) {
1231
0
                tableBorder = tblBorder[0];
1232
0
                if (tblBorderBrush[0].color().isValid())
1233
0
                    borderBrush = tblBorderBrush[0];
1234
0
                if (tblBorderStyle[0] != static_cast<QTextFrameFormat::BorderStyle>(-1))
1235
0
                    borderStyle = tblBorderStyle[0];
1236
0
            }
1237
0
        }
1238
0
        break;
1239
0
        case QCss::BorderCollapse:
1240
0
            borderCollapse = decl.borderCollapseValue();
1241
0
            break;
1242
0
        case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
1243
0
        case QCss::Float:
1244
0
            cssFloat = QTextFrameFormat::InFlow;
1245
0
            switch (identifier) {
1246
0
            case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
1247
0
            case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
1248
0
            default: break;
1249
0
            }
1250
0
            break;
1251
0
        case QCss::QtBlockIndent:
1252
0
            blockFormat.setIndent(decl.d->values.constFirst().variant.toInt());
1253
0
            break;
1254
0
        case QCss::QtLineHeightType: {
1255
0
            QString lineHeightTypeName = decl.d->values.constFirst().variant.toString();
1256
0
            QTextBlockFormat::LineHeightTypes lineHeightType;
1257
0
            if (lineHeightTypeName.compare("proportional"_L1, Qt::CaseInsensitive) == 0)
1258
0
                lineHeightType = QTextBlockFormat::ProportionalHeight;
1259
0
            else if (lineHeightTypeName.compare("fixed"_L1, Qt::CaseInsensitive) == 0)
1260
0
                lineHeightType = QTextBlockFormat::FixedHeight;
1261
0
            else if (lineHeightTypeName.compare("minimum"_L1, Qt::CaseInsensitive) == 0)
1262
0
                lineHeightType = QTextBlockFormat::MinimumHeight;
1263
0
            else if (lineHeightTypeName.compare("line-distance"_L1, Qt::CaseInsensitive) == 0)
1264
0
                lineHeightType = QTextBlockFormat::LineDistanceHeight;
1265
0
            else
1266
0
                lineHeightType = QTextBlockFormat::SingleHeight;
1267
1268
0
            if (hasLineHeightMultiplier) {
1269
0
                qreal lineHeight = blockFormat.lineHeight() / 100.0;
1270
0
                blockFormat.setProperty(QTextBlockFormat::LineHeight, lineHeight);
1271
0
            }
1272
1273
0
            blockFormat.setProperty(QTextBlockFormat::LineHeightType, lineHeightType);
1274
0
            hasOwnLineHeightType = true;
1275
0
        }
1276
0
        break;
1277
0
        case QCss::LineHeight: {
1278
0
            qreal lineHeight;
1279
0
            QTextBlockFormat::LineHeightTypes lineHeightType;
1280
0
            if (decl.realValue(&lineHeight, "px")) {
1281
0
                lineHeightType = QTextBlockFormat::MinimumHeight;
1282
0
            } else {
1283
0
                bool ok;
1284
0
                QCss::Value cssValue = decl.d->values.constFirst();
1285
0
                QString value = cssValue.toString();
1286
0
                lineHeight = value.toDouble(&ok);
1287
0
                if (ok) {
1288
0
                    if (!hasOwnLineHeightType && cssValue.type == QCss::Value::Number) {
1289
0
                        lineHeight *= 100.0;
1290
0
                        hasLineHeightMultiplier = true;
1291
0
                    }
1292
0
                    lineHeightType = QTextBlockFormat::ProportionalHeight;
1293
0
                } else {
1294
0
                    lineHeight = 0.0;
1295
0
                    lineHeightType = QTextBlockFormat::SingleHeight;
1296
0
                }
1297
0
            }
1298
1299
            // Only override line height type if specified in same node
1300
0
            if (hasOwnLineHeightType)
1301
0
                lineHeightType = QTextBlockFormat::LineHeightTypes(blockFormat.lineHeightType());
1302
1303
0
            blockFormat.setLineHeight(lineHeight, lineHeightType);
1304
0
            break;
1305
0
        }
1306
0
        case QCss::TextIndent: {
1307
0
            qreal indent = 0;
1308
0
            if (decl.realValue(&indent, "px"))
1309
0
                blockFormat.setTextIndent(indent);
1310
0
            break; }
1311
0
        case QCss::QtListIndent:
1312
0
            if (decl.intValue(&cssListIndent))
1313
0
                hasCssListIndent = true;
1314
0
            break;
1315
0
        case QCss::QtParagraphType:
1316
0
            if (decl.d->values.constFirst().variant.toString().compare("empty"_L1, Qt::CaseInsensitive) == 0)
1317
0
                isEmptyParagraph = true;
1318
0
            break;
1319
0
        case QCss::QtTableType:
1320
0
            if (decl.d->values.constFirst().variant.toString().compare("frame"_L1, Qt::CaseInsensitive) == 0)
1321
0
                isTextFrame = true;
1322
0
            else if (decl.d->values.constFirst().variant.toString().compare("root"_L1, Qt::CaseInsensitive) == 0) {
1323
0
                isTextFrame = true;
1324
0
                isRootFrame = true;
1325
0
            }
1326
0
            break;
1327
0
        case QCss::QtUserState:
1328
0
            userState = decl.d->values.constFirst().variant.toInt();
1329
0
            break;
1330
0
        case QCss::Whitespace:
1331
0
            switch (identifier) {
1332
0
            case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
1333
0
            case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
1334
0
            case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
1335
0
            case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
1336
0
            case QCss::Value_PreLine: wsm = QTextHtmlParserNode::WhiteSpacePreLine; break;
1337
0
            default: break;
1338
0
            }
1339
0
            break;
1340
0
        case QCss::VerticalAlignment:
1341
0
            switch (identifier) {
1342
0
            case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
1343
0
            case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
1344
0
            case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
1345
0
            case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
1346
0
            case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
1347
0
            default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
1348
0
            }
1349
0
            break;
1350
0
        case QCss::PageBreakBefore:
1351
0
            switch (identifier) {
1352
0
            case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
1353
0
            case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
1354
0
            default: break;
1355
0
            }
1356
0
            break;
1357
0
        case QCss::PageBreakAfter:
1358
0
            switch (identifier) {
1359
0
            case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
1360
0
            case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
1361
0
            default: break;
1362
0
            }
1363
0
            break;
1364
0
        case QCss::TextUnderlineStyle:
1365
0
            switch (identifier) {
1366
0
            case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
1367
0
            case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
1368
0
            case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
1369
0
            case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
1370
0
            case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
1371
0
            case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
1372
0
            case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
1373
0
            default: break;
1374
0
            }
1375
0
            break;
1376
0
        case QCss::TextDecorationColor: charFormat.setUnderlineColor(decl.colorValue()); break;
1377
0
        case QCss::ListStyleType:
1378
0
        case QCss::ListStyle:
1379
0
            setListStyle(decl.d->values);
1380
0
            break;
1381
0
        case QCss::QtListNumberPrefix:
1382
0
            textListNumberPrefix = decl.d->values.constFirst().variant.toString();
1383
0
            break;
1384
0
        case QCss::QtListNumberSuffix:
1385
0
            textListNumberSuffix = decl.d->values.constFirst().variant.toString();
1386
0
            break;
1387
0
        case QCss::TextAlignment:
1388
0
            switch (identifier) {
1389
0
            case QCss::Value_Left: blockFormat.setAlignment(Qt::AlignLeft); break;
1390
0
            case QCss::Value_Center: blockFormat.setAlignment(Qt::AlignCenter); break;
1391
0
            case QCss::Value_Right: blockFormat.setAlignment(Qt::AlignRight); break;
1392
0
            default: break;
1393
0
            }
1394
0
            break;
1395
1396
0
        case QCss::QtForegroundTextureCacheKey:
1397
0
        {
1398
0
            if (resourceProvider != nullptr && QTextDocumentPrivate::get(resourceProvider) != nullptr) {
1399
0
                bool ok;
1400
0
                qint64 searchKey = decl.d->values.constFirst().variant.toLongLong(&ok);
1401
0
                if (ok)
1402
0
                    applyForegroundImage(searchKey, resourceProvider);
1403
0
            }
1404
0
            break;
1405
0
        }
1406
0
        case QCss::QtStrokeColor:
1407
0
        {
1408
0
            QPen pen = charFormat.textOutline();
1409
0
            pen.setStyle(Qt::SolidLine);
1410
0
            pen.setColor(decl.colorValue());
1411
0
            charFormat.setTextOutline(pen);
1412
0
            break;
1413
0
        }
1414
0
        case QCss::QtStrokeWidth:
1415
0
        {
1416
0
            qreal width;
1417
0
            if (decl.realValue(&width, "px")) {
1418
0
                QPen pen = charFormat.textOutline();
1419
0
                pen.setWidthF(width);
1420
0
                charFormat.setTextOutline(pen);
1421
0
            }
1422
0
            break;
1423
0
        }
1424
0
        case QCss::QtStrokeLineCap:
1425
0
        {
1426
0
            QPen pen = charFormat.textOutline();
1427
0
            switch (identifier) {
1428
0
            case QCss::Value_SquareCap: pen.setCapStyle(Qt::SquareCap); break;
1429
0
            case QCss::Value_FlatCap: pen.setCapStyle(Qt::FlatCap); break;
1430
0
            case QCss::Value_RoundCap: pen.setCapStyle(Qt::RoundCap); break;
1431
0
            default: break;
1432
0
            }
1433
0
            charFormat.setTextOutline(pen);
1434
0
            break;
1435
0
        }
1436
0
        case QCss::QtStrokeLineJoin:
1437
0
        {
1438
0
            QPen pen = charFormat.textOutline();
1439
0
            switch (identifier) {
1440
0
            case QCss::Value_MiterJoin: pen.setJoinStyle(Qt::MiterJoin); break;
1441
0
            case QCss::Value_BevelJoin: pen.setJoinStyle(Qt::BevelJoin); break;
1442
0
            case QCss::Value_RoundJoin: pen.setJoinStyle(Qt::RoundJoin); break;
1443
0
            case QCss::Value_SvgMiterJoin: pen.setJoinStyle(Qt::SvgMiterJoin); break;
1444
0
            default: break;
1445
0
            }
1446
0
            charFormat.setTextOutline(pen);
1447
0
            break;
1448
0
        }
1449
0
        case QCss::QtStrokeMiterLimit:
1450
0
        {
1451
0
            qreal miterLimit;
1452
0
            if (decl.realValue(&miterLimit)) {
1453
0
                QPen pen = charFormat.textOutline();
1454
0
                pen.setMiterLimit(miterLimit);
1455
0
                charFormat.setTextOutline(pen);
1456
0
            }
1457
0
            break;
1458
0
        }
1459
0
        case QCss::QtStrokeDashArray:
1460
0
        {
1461
0
            QList<qreal> dashes = decl.dashArray();
1462
0
            if (!dashes.empty()) {
1463
0
                QPen pen = charFormat.textOutline();
1464
0
                pen.setDashPattern(dashes);
1465
0
                charFormat.setTextOutline(pen);
1466
0
            }
1467
0
            break;
1468
0
        }
1469
0
        case QCss::QtStrokeDashOffset:
1470
0
        {
1471
0
            qreal dashOffset;
1472
0
            if (decl.realValue(&dashOffset)) {
1473
0
                QPen pen = charFormat.textOutline();
1474
0
                pen.setDashOffset(dashOffset);
1475
0
                charFormat.setTextOutline(pen);
1476
0
            }
1477
0
            break;
1478
0
        }
1479
0
        case QCss::QtForeground:
1480
0
        {
1481
0
            QBrush brush = decl.brushValue();
1482
0
            charFormat.setForeground(brush);
1483
0
            break;
1484
0
        }
1485
0
        case QCss::MaximumWidth:
1486
0
            if (id == Html_img) {
1487
0
                auto imageFormat = charFormat.toImageFormat();
1488
0
                imageFormat.setMaximumWidth(extractor.textLength(decl));
1489
0
                charFormat = imageFormat;
1490
0
            }
1491
0
            break;
1492
0
        default: break;
1493
0
        }
1494
0
    }
1495
1496
0
    QFont f;
1497
0
    int adjustment = -255;
1498
0
    extractor.extractFont(&f, &adjustment);
1499
0
    if (f.pixelSize() > INT32_MAX / 2)
1500
0
        f.setPixelSize(INT32_MAX / 2);   // avoid even more extreme values
1501
0
    charFormat.setFont(f, QTextCharFormat::FontPropertiesSpecifiedOnly);
1502
1503
0
    if (adjustment >= -1)
1504
0
        charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
1505
1506
0
    {
1507
0
        Qt::Alignment ignoredAlignment;
1508
0
        QCss::Repeat ignoredRepeat;
1509
0
        QString bgImage;
1510
0
        QBrush bgBrush;
1511
0
        QCss::Origin ignoredOrigin, ignoredClip;
1512
0
        QCss::Attachment ignoredAttachment;
1513
0
        extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
1514
0
                                    &ignoredOrigin, &ignoredAttachment, &ignoredClip);
1515
1516
0
        if (!bgImage.isEmpty() && resourceProvider) {
1517
0
            applyBackgroundImage(bgImage, resourceProvider);
1518
0
        } else if (bgBrush.style() != Qt::NoBrush) {
1519
0
            charFormat.setBackground(bgBrush);
1520
0
            if (id == Html_hr)
1521
0
                blockFormat.setProperty(QTextFormat::BackgroundBrush, bgBrush);
1522
0
        }
1523
0
    }
1524
0
}
1525
1526
#endif // QT_NO_CSSPARSER
1527
1528
void QTextHtmlParserNode::applyForegroundImage(qint64 searchKey, const QTextDocument *resourceProvider)
1529
0
{
1530
0
    const QTextDocumentPrivate *priv = QTextDocumentPrivate::get(resourceProvider);
1531
0
    for (int i = 0; i < priv->formats.numFormats(); ++i) {
1532
0
        QTextCharFormat format = priv->formats.charFormat(i);
1533
0
        if (format.isValid()) {
1534
0
            QBrush brush = format.foreground();
1535
0
            if (brush.style() == Qt::TexturePattern) {
1536
0
                const bool isPixmap = qHasPixmapTexture(brush);
1537
1538
0
                if (isPixmap && !QThread::isMainThread()) {
1539
0
                    qWarning("Can't apply QPixmap outside of GUI thread");
1540
0
                    return;
1541
0
                }
1542
1543
0
                const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
1544
0
                if (cacheKey == searchKey) {
1545
0
                    QBrush b;
1546
0
                    if (isPixmap)
1547
0
                        b.setTexture(brush.texture());
1548
0
                    else
1549
0
                        b.setTextureImage(brush.textureImage());
1550
0
                    b.setStyle(Qt::TexturePattern);
1551
0
                    charFormat.setForeground(b);
1552
0
                }
1553
0
            }
1554
0
        }
1555
0
    }
1556
1557
0
}
1558
1559
void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
1560
0
{
1561
0
    if (!url.isEmpty() && resourceProvider) {
1562
0
        QVariant val = resourceProvider->resource(QTextDocument::ImageResource, QUrl{url});
1563
1564
0
        if (!QThread::isMainThread()) {
1565
            // must use images in non-GUI threads
1566
0
            if (val.userType() == QMetaType::QImage) {
1567
0
                QImage image = qvariant_cast<QImage>(val);
1568
0
                charFormat.setBackground(image);
1569
0
            } else if (val.userType() == QMetaType::QByteArray) {
1570
0
                QImage image;
1571
0
                if (image.loadFromData(val.toByteArray())) {
1572
0
                    charFormat.setBackground(image);
1573
0
                }
1574
0
            }
1575
0
        } else {
1576
0
            if (val.userType() == QMetaType::QImage || val.userType() == QMetaType::QPixmap) {
1577
0
                charFormat.setBackground(qvariant_cast<QPixmap>(val));
1578
0
            } else if (val.userType() == QMetaType::QByteArray) {
1579
0
                QPixmap pm;
1580
0
                if (pm.loadFromData(val.toByteArray())) {
1581
0
                    charFormat.setBackground(pm);
1582
0
                }
1583
0
            }
1584
0
        }
1585
0
    }
1586
0
    if (!url.isEmpty())
1587
0
        charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
1588
0
}
1589
1590
bool QTextHtmlParserNode::hasOnlyWhitespace() const
1591
0
{
1592
0
    for (int i = 0; i < text.size(); ++i)
1593
0
        if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
1594
0
            return false;
1595
0
    return true;
1596
0
}
1597
1598
static bool setIntAttribute(int *destination, const QString &value)
1599
0
{
1600
0
    bool ok = false;
1601
0
    int val = value.toInt(&ok);
1602
0
    if (ok)
1603
0
        *destination = val;
1604
1605
0
    return ok;
1606
0
}
1607
1608
static bool setFloatAttribute(qreal *destination, const QString &value)
1609
0
{
1610
0
    bool ok = false;
1611
0
    qreal val = value.toDouble(&ok);
1612
0
    if (ok)
1613
0
        *destination = val;
1614
1615
0
    return ok;
1616
0
}
1617
1618
static void setWidthAttribute(QTextLength *width, const QString &valueStr)
1619
0
{
1620
0
    bool ok = false;
1621
0
    qreal realVal = valueStr.toDouble(&ok);
1622
0
    if (ok) {
1623
0
        *width = QTextLength(QTextLength::FixedLength, realVal);
1624
0
    } else {
1625
0
        auto value = QStringView(valueStr).trimmed();
1626
0
        if (!value.isEmpty() && value.endsWith(u'%')) {
1627
0
            value.truncate(value.size() - 1);
1628
0
            realVal = value.toDouble(&ok);
1629
0
            if (ok)
1630
0
                *width = QTextLength(QTextLength::PercentageLength, realVal);
1631
0
        }
1632
0
    }
1633
0
}
1634
1635
#ifndef QT_NO_CSSPARSER
1636
void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
1637
0
{
1638
0
    const QString css = "* {"_L1 + value + u'}';
1639
0
    QCss::Parser parser(css);
1640
0
    QCss::StyleSheet sheet;
1641
0
    parser.parse(&sheet, Qt::CaseInsensitive);
1642
0
    if (sheet.styleRules.size() != 1) return;
1643
0
    applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
1644
0
}
1645
#endif
1646
1647
QStringList QTextHtmlParser::parseAttributes()
1648
0
{
1649
0
    QStringList attrs;
1650
1651
0
    while (pos < len) {
1652
0
        eatSpace();
1653
0
        if (hasPrefix(u'>') || hasPrefix(u'/'))
1654
0
            break;
1655
0
        QString key = parseWord().toLower();
1656
0
        QString value = "1"_L1;
1657
0
        if (key.size() == 0)
1658
0
            break;
1659
0
        eatSpace();
1660
0
        if (hasPrefix(u'=')){
1661
0
            pos++;
1662
0
            eatSpace();
1663
0
            value = parseWord();
1664
0
        }
1665
0
        if (value.size() == 0)
1666
0
            continue;
1667
0
        attrs << key << value;
1668
0
    }
1669
1670
0
    return attrs;
1671
0
}
1672
1673
void QTextHtmlParser::applyAttributes(const QStringList &attributes)
1674
0
{
1675
    // local state variable for qt3 textedit mode
1676
0
    bool seenQt3Richtext = false;
1677
0
    QString linkHref;
1678
0
    QString linkType;
1679
1680
0
    if (attributes.size() % 2 == 1)
1681
0
        return;
1682
1683
0
    QTextHtmlParserNode *node = nodes.last();
1684
1685
0
    for (int i = 0; i < attributes.size(); i += 2) {
1686
0
        QString key = attributes.at(i);
1687
0
        QString value = attributes.at(i + 1);
1688
1689
0
        switch (node->id) {
1690
0
            case Html_font:
1691
                // the infamous font tag
1692
0
                if (key == "size"_L1 && value.size()) {
1693
0
                    int n = value.toInt();
1694
0
                    if (value.at(0) != u'+' && value.at(0) != u'-')
1695
0
                        n -= 3;
1696
0
                    node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
1697
0
                } else if (key == "face"_L1) {
1698
0
                    if (value.contains(u',')) {
1699
0
                        QStringList families;
1700
0
                        for (auto family : value.tokenize(u','))
1701
0
                            families << family.trimmed().toString();
1702
0
                        node->charFormat.setFontFamilies(families);
1703
0
                    } else {
1704
0
                        node->charFormat.setFontFamilies(QStringList(value));
1705
0
                    }
1706
0
                } else if (key == "color"_L1) {
1707
0
                    QColor c = QColor::fromString(value);
1708
0
                    if (!c.isValid())
1709
0
                        qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1710
0
                    node->charFormat.setForeground(c);
1711
0
                }
1712
0
                break;
1713
0
            case Html_ol:
1714
0
            case Html_ul:
1715
0
                if (key == "type"_L1) {
1716
0
                    node->hasOwnListStyle = true;
1717
0
                    if (value == "1"_L1) {
1718
0
                        node->listStyle = QTextListFormat::ListDecimal;
1719
0
                    } else if (value == "a"_L1) {
1720
0
                        node->listStyle = QTextListFormat::ListLowerAlpha;
1721
0
                    } else if (value == "A"_L1) {
1722
0
                        node->listStyle = QTextListFormat::ListUpperAlpha;
1723
0
                    } else if (value == "i"_L1) {
1724
0
                        node->listStyle = QTextListFormat::ListLowerRoman;
1725
0
                    } else if (value == "I"_L1) {
1726
0
                        node->listStyle = QTextListFormat::ListUpperRoman;
1727
0
                    } else {
1728
0
                        value = std::move(value).toLower();
1729
0
                        if (value == "square"_L1)
1730
0
                            node->listStyle = QTextListFormat::ListSquare;
1731
0
                        else if (value == "disc"_L1)
1732
0
                            node->listStyle = QTextListFormat::ListDisc;
1733
0
                        else if (value == "circle"_L1)
1734
0
                            node->listStyle = QTextListFormat::ListCircle;
1735
0
                        else if (value == "none"_L1)
1736
0
                            node->listStyle = QTextListFormat::ListStyleUndefined;
1737
0
                    }
1738
0
                } else if (key == "start"_L1) {
1739
0
                    setIntAttribute(&node->listStart, value);
1740
0
                }
1741
0
                break;
1742
0
            case Html_li:
1743
0
                if (key == "class"_L1) {
1744
0
                    if (value == "unchecked"_L1)
1745
0
                        node->blockFormat.setMarker(QTextBlockFormat::MarkerType::Unchecked);
1746
0
                    else if (value == "checked"_L1)
1747
0
                        node->blockFormat.setMarker(QTextBlockFormat::MarkerType::Checked);
1748
0
                }
1749
0
                break;
1750
0
            case Html_a:
1751
0
                if (key == "href"_L1)
1752
0
                    node->charFormat.setAnchorHref(value);
1753
0
                else if (key == "name"_L1)
1754
0
                    node->charFormat.setAnchorNames({value});
1755
0
                break;
1756
0
            case Html_img:
1757
0
                if (key == "src"_L1 || key == "source"_L1) {
1758
0
                    node->imageName = value;
1759
0
                } else if (key == "width"_L1) {
1760
0
                    node->imageWidth = -2; // register that there is a value for it.
1761
0
                    setFloatAttribute(&node->imageWidth, value);
1762
0
                } else if (key == "height"_L1) {
1763
0
                    node->imageHeight = -2; // register that there is a value for it.
1764
0
                    setFloatAttribute(&node->imageHeight, value);
1765
0
                } else if (key == "alt"_L1) {
1766
0
                    node->imageAlt = value;
1767
0
                } else if (key == "title"_L1) {
1768
0
                    node->text = value;
1769
0
                }
1770
0
                break;
1771
0
            case Html_tr:
1772
0
            case Html_body:
1773
0
                if (key == "bgcolor"_L1) {
1774
0
                    QColor c = QColor::fromString(value);
1775
0
                    if (!c.isValid())
1776
0
                        qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1777
0
                    node->charFormat.setBackground(c);
1778
0
                } else if (key == "background"_L1) {
1779
0
                    node->applyBackgroundImage(value, resourceProvider);
1780
0
                }
1781
0
                break;
1782
0
            case Html_th:
1783
0
            case Html_td:
1784
0
                if (key == "width"_L1) {
1785
0
                    setWidthAttribute(&node->width, value);
1786
0
                } else if (key == "bgcolor"_L1) {
1787
0
                    QColor c = QColor::fromString(value);
1788
0
                    if (!c.isValid())
1789
0
                        qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1790
0
                    node->charFormat.setBackground(c);
1791
0
                } else if (key == "background"_L1) {
1792
0
                    node->applyBackgroundImage(value, resourceProvider);
1793
0
                } else if (key == "rowspan"_L1) {
1794
0
                    if (setIntAttribute(&node->tableCellRowSpan, value))
1795
0
                        node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
1796
0
                } else if (key == "colspan"_L1) {
1797
0
                    if (setIntAttribute(&node->tableCellColSpan, value))
1798
0
                        node->tableCellColSpan = qBound(1, node->tableCellColSpan, 20480);
1799
0
                }
1800
0
                break;
1801
0
            case Html_table:
1802
                // If table border already set through css style, prefer that one otherwise consider this value
1803
0
                if (key == "border"_L1 && !node->tableBorder) {
1804
0
                    setFloatAttribute(&node->tableBorder, value);
1805
0
                } else if (key == "bgcolor"_L1) {
1806
0
                    QColor c = QColor::fromString(value);
1807
0
                    if (!c.isValid())
1808
0
                        qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1809
0
                    node->charFormat.setBackground(c);
1810
0
                } else if (key == "bordercolor"_L1) {
1811
0
                    QColor c = QColor::fromString(value);
1812
0
                    if (!c.isValid())
1813
0
                        qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1814
0
                    node->borderBrush = c;
1815
0
                } else if (key == "background"_L1) {
1816
0
                    node->applyBackgroundImage(value, resourceProvider);
1817
0
                } else if (key == "cellspacing"_L1) {
1818
0
                    setFloatAttribute(&node->tableCellSpacing, value);
1819
0
                } else if (key == "cellpadding"_L1) {
1820
0
                    setFloatAttribute(&node->tableCellPadding, value);
1821
0
                } else if (key == "width"_L1) {
1822
0
                    setWidthAttribute(&node->width, value);
1823
0
                } else if (key == "height"_L1) {
1824
0
                    setWidthAttribute(&node->height, value);
1825
0
                }
1826
0
                break;
1827
0
            case Html_meta:
1828
0
                if (key == "name"_L1 && value == "qrichtext"_L1)
1829
0
                    seenQt3Richtext = true;
1830
1831
0
                if (key == "content"_L1 && value == "1"_L1 && seenQt3Richtext)
1832
0
                    textEditMode = true;
1833
0
                break;
1834
0
            case Html_hr:
1835
0
                if (key == "width"_L1)
1836
0
                    setWidthAttribute(&node->width, value);
1837
0
                break;
1838
0
            case Html_link:
1839
0
                if (key == "href"_L1)
1840
0
                    linkHref = value;
1841
0
                else if (key == "type"_L1)
1842
0
                    linkType = value;
1843
0
                break;
1844
0
            case Html_pre:
1845
0
                if (key == "class"_L1 && value.startsWith("language-"_L1))
1846
0
                    node->blockFormat.setProperty(QTextFormat::BlockCodeLanguage, value.mid(9));
1847
0
                break;
1848
0
            default:
1849
0
                break;
1850
0
        }
1851
1852
0
        if (key == "style"_L1) {
1853
0
#ifndef QT_NO_CSSPARSER
1854
0
            node->parseStyleAttribute(value, resourceProvider);
1855
0
#endif
1856
0
        } else if (key == "align"_L1) {
1857
0
            value = std::move(value).toLower();
1858
0
            bool alignmentSet = true;
1859
1860
0
            if (value == "left"_L1)
1861
0
                node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
1862
0
            else if (value == "right"_L1)
1863
0
                node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
1864
0
            else if (value == "center"_L1)
1865
0
                node->blockFormat.setAlignment(Qt::AlignHCenter);
1866
0
            else if (value == "justify"_L1)
1867
0
                node->blockFormat.setAlignment(Qt::AlignJustify);
1868
0
            else
1869
0
                alignmentSet = false;
1870
1871
0
            if (node->id == Html_img) {
1872
                // HTML4 compat
1873
0
                if (alignmentSet) {
1874
0
                    if (node->blockFormat.alignment() & Qt::AlignLeft)
1875
0
                        node->cssFloat = QTextFrameFormat::FloatLeft;
1876
0
                    else if (node->blockFormat.alignment() & Qt::AlignRight)
1877
0
                        node->cssFloat = QTextFrameFormat::FloatRight;
1878
0
                } else if (value == "middle"_L1) {
1879
0
                    node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1880
0
                } else if (value == "top"_L1) {
1881
0
                    node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1882
0
                }
1883
0
            }
1884
0
        } else if (key == "valign"_L1) {
1885
0
            value = std::move(value).toLower();
1886
0
            if (value == "top"_L1)
1887
0
                node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1888
0
            else if (value == "middle"_L1)
1889
0
                node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1890
0
            else if (value == "bottom"_L1)
1891
0
                node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
1892
0
        } else if (key == "dir"_L1) {
1893
0
            value = std::move(value).toLower();
1894
0
            if (value == "ltr"_L1)
1895
0
                node->blockFormat.setLayoutDirection(Qt::LeftToRight);
1896
0
            else if (value == "rtl"_L1)
1897
0
                node->blockFormat.setLayoutDirection(Qt::RightToLeft);
1898
0
        } else if (key == "title"_L1) {
1899
0
            node->charFormat.setToolTip(value);
1900
0
        } else if (key == "id"_L1) {
1901
0
            node->charFormat.setAnchor(true);
1902
0
            node->charFormat.setAnchorNames({value});
1903
0
        }
1904
0
    }
1905
1906
0
#ifndef QT_NO_CSSPARSER
1907
0
    if (resourceProvider && !linkHref.isEmpty() && linkType == "text/css"_L1)
1908
0
        importStyleSheet(linkHref);
1909
0
#endif
1910
0
}
1911
1912
#ifndef QT_NO_CSSPARSER
1913
class QTextHtmlStyleSelector : public QCss::StyleSelector
1914
{
1915
public:
1916
    inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
1917
0
        : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
1918
1919
    bool nodeNameEquals(NodePtr node, const QString& nodeName) const override;
1920
    QStringList nodeNames(NodePtr node) const override;
1921
    QString attributeValue(NodePtr node, const QCss::AttributeSelector &aSelector) const override;
1922
    bool hasAttributes(NodePtr node) const override;
1923
    bool isNullNode(NodePtr node) const override;
1924
    NodePtr parentNode(NodePtr node) const override;
1925
    NodePtr previousSiblingNode(NodePtr node) const override;
1926
    NodePtr duplicateNode(NodePtr node) const override;
1927
    void freeNode(NodePtr node) const override;
1928
1929
private:
1930
    const QTextHtmlParser *parser;
1931
};
1932
1933
bool QTextHtmlStyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
1934
0
{
1935
0
    return parser->at(node.id).tag.compare(nodeName, nameCaseSensitivity) == 0;
1936
0
}
1937
1938
QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
1939
0
{
1940
0
    return QStringList(parser->at(node.id).tag.toLower());
1941
0
}
1942
1943
#endif // QT_NO_CSSPARSER
1944
1945
#ifndef QT_NO_CSSPARSER
1946
1947
static inline int findAttribute(const QStringList &attributes, const QString &name)
1948
0
{
1949
0
    int idx = -1;
1950
0
    do {
1951
0
        idx = attributes.indexOf(name, idx + 1);
1952
0
    } while (idx != -1 && (idx % 2 == 1));
1953
0
    return idx;
1954
0
}
1955
1956
QString QTextHtmlStyleSelector::attributeValue(NodePtr node, const QCss::AttributeSelector &aSelector) const
1957
0
{
1958
0
    const QStringList &attributes = parser->at(node.id).attributes;
1959
0
    const int idx = findAttribute(attributes, aSelector.name);
1960
0
    if (idx == -1)
1961
0
        return QString();
1962
0
    return attributes.at(idx + 1);
1963
0
}
1964
1965
bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
1966
0
{
1967
0
   const QStringList &attributes = parser->at(node.id).attributes;
1968
0
   return !attributes.isEmpty();
1969
0
}
1970
1971
bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
1972
0
{
1973
0
    return node.id == 0;
1974
0
}
1975
1976
QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
1977
0
{
1978
0
    NodePtr parent;
1979
0
    parent.id = 0;
1980
0
    if (node.id) {
1981
0
        parent.id = parser->at(node.id).parent;
1982
0
    }
1983
0
    return parent;
1984
0
}
1985
1986
QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
1987
0
{
1988
0
    return node;
1989
0
}
1990
1991
QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
1992
0
{
1993
0
    NodePtr sibling;
1994
0
    sibling.id = 0;
1995
0
    if (!node.id)
1996
0
        return sibling;
1997
0
    int parent = parser->at(node.id).parent;
1998
0
    if (!parent)
1999
0
        return sibling;
2000
0
    const int childIdx = parser->at(parent).children.indexOf(node.id);
2001
0
    if (childIdx <= 0)
2002
0
        return sibling;
2003
0
    sibling.id = parser->at(parent).children.at(childIdx - 1);
2004
0
    return sibling;
2005
0
}
2006
2007
void QTextHtmlStyleSelector::freeNode(NodePtr) const
2008
0
{
2009
0
}
2010
2011
void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
2012
0
{
2013
0
    for (int i = 0; i < sheet.importRules.size(); ++i) {
2014
0
        const QCss::ImportRule &rule = sheet.importRules.at(i);
2015
0
        if (rule.media.isEmpty() || rule.media.contains("screen"_L1, Qt::CaseInsensitive))
2016
0
            importStyleSheet(rule.href);
2017
0
    }
2018
0
}
2019
2020
void QTextHtmlParser::importStyleSheet(const QString &href)
2021
0
{
2022
0
    if (!resourceProvider)
2023
0
        return;
2024
0
    for (int i = 0; i < externalStyleSheets.size(); ++i)
2025
0
        if (externalStyleSheets.at(i).url == href)
2026
0
            return;
2027
2028
0
    QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, QUrl{href});
2029
0
    QString css;
2030
0
    if (res.userType() == QMetaType::QString) {
2031
0
        css = res.toString();
2032
0
    } else if (res.userType() == QMetaType::QByteArray) {
2033
        // #### detect @charset
2034
0
        css = QString::fromUtf8(res.toByteArray());
2035
0
    }
2036
0
    if (!css.isEmpty()) {
2037
0
        QCss::Parser parser(css);
2038
0
        QCss::StyleSheet sheet;
2039
0
        parser.parse(&sheet, Qt::CaseInsensitive);
2040
0
        externalStyleSheets.append(ExternalStyleSheet(href, sheet));
2041
0
        resolveStyleSheetImports(sheet);
2042
0
    }
2043
0
}
2044
2045
QList<QCss::Declaration> standardDeclarationForNode(const QTextHtmlParserNode &node)
2046
0
{
2047
0
    QList<QCss::Declaration> decls;
2048
0
    QCss::Declaration decl;
2049
0
    QCss::Value val;
2050
0
    switch (node.id) {
2051
0
    case Html_a:
2052
0
    case Html_u: {
2053
0
        bool needsUnderline = (node.id == Html_u) ? true : false;
2054
0
        if (node.id == Html_a) {
2055
0
            for (int i = 0; i < node.attributes.size(); i += 2) {
2056
0
                const QString key = node.attributes.at(i);
2057
0
                if (key.compare("href"_L1, Qt::CaseInsensitive) == 0
2058
0
                    && !node.attributes.at(i + 1).isEmpty()) {
2059
0
                    needsUnderline = true;
2060
0
                    decl.d->property = "color"_L1;
2061
0
                    decl.d->propertyId = QCss::Color;
2062
0
                    val.type = QCss::Value::Function;
2063
0
                    val.variant = QStringList() << "palette"_L1 << "link"_L1;
2064
0
                    decl.d->values = QList<QCss::Value> { val };
2065
0
                    decl.d->inheritable = true;
2066
0
                    decls << decl;
2067
0
                    break;
2068
0
                }
2069
0
            }
2070
0
        }
2071
0
        if (needsUnderline) {
2072
0
            decl = QCss::Declaration();
2073
0
            decl.d->property = "text-decoration"_L1;
2074
0
            decl.d->propertyId = QCss::TextDecoration;
2075
0
            val.type = QCss::Value::KnownIdentifier;
2076
0
            val.variant = QVariant(QCss::Value_Underline);
2077
0
            decl.d->values = QList<QCss::Value> { val };
2078
0
            decl.d->inheritable = true;
2079
0
            decls << decl;
2080
0
        }
2081
0
        break;
2082
0
    }
2083
0
    case Html_b:
2084
0
    case Html_strong:
2085
0
    case Html_h1:
2086
0
    case Html_h2:
2087
0
    case Html_h3:
2088
0
    case Html_h4:
2089
0
    case Html_h5:
2090
0
    case Html_th:
2091
0
        decl = QCss::Declaration();
2092
0
        decl.d->property = "font-weight"_L1;
2093
0
        decl.d->propertyId = QCss::FontWeight;
2094
0
        val.type = QCss::Value::KnownIdentifier;
2095
0
        val.variant = QVariant(QCss::Value_Bold);
2096
0
        decl.d->values = QList<QCss::Value> { val };
2097
0
        decl.d->inheritable = true;
2098
0
        decls << decl;
2099
0
        if (node.id == Html_b || node.id == Html_strong)
2100
0
            break;
2101
0
        Q_FALLTHROUGH();
2102
0
    case Html_big:
2103
0
    case Html_small:
2104
0
        if (node.id != Html_th) {
2105
0
            decl = QCss::Declaration();
2106
0
            decl.d->property = "font-size"_L1;
2107
0
            decl.d->propertyId = QCss::FontSize;
2108
0
            decl.d->inheritable = false;
2109
0
            val.type = QCss::Value::KnownIdentifier;
2110
0
            switch (node.id) {
2111
0
            case Html_h1: val.variant = QVariant(QCss::Value_XXLarge); break;
2112
0
            case Html_h2: val.variant = QVariant(QCss::Value_XLarge); break;
2113
0
            case Html_h3: case Html_big: val.variant = QVariant(QCss::Value_Large); break;
2114
0
            case Html_h4: val.variant = QVariant(QCss::Value_Medium); break;
2115
0
            case Html_h5: case Html_small: val.variant = QVariant(QCss::Value_Small); break;
2116
0
            default: break;
2117
0
            }
2118
0
            decl.d->values = QList<QCss::Value> { val };
2119
0
            decls << decl;
2120
0
            break;
2121
0
        }
2122
0
        Q_FALLTHROUGH();
2123
0
    case Html_center:
2124
0
    case Html_td:
2125
0
        decl = QCss::Declaration();
2126
0
        decl.d->property = "text-align"_L1;
2127
0
        decl.d->propertyId = QCss::TextAlignment;
2128
0
        val.type = QCss::Value::KnownIdentifier;
2129
0
        val.variant = (node.id == Html_td) ? QVariant(QCss::Value_Left) : QVariant(QCss::Value_Center);
2130
0
        decl.d->values = QList<QCss::Value> { val };
2131
0
        decl.d->inheritable = true;
2132
0
        decls << decl;
2133
0
        break;
2134
0
    case Html_del:
2135
0
    case Html_s:
2136
0
        decl = QCss::Declaration();
2137
0
        decl.d->property = "text-decoration"_L1;
2138
0
        decl.d->propertyId = QCss::TextDecoration;
2139
0
        val.type = QCss::Value::KnownIdentifier;
2140
0
        val.variant = QVariant(QCss::Value_LineThrough);
2141
0
        decl.d->values = QList<QCss::Value> { val };
2142
0
        decl.d->inheritable = true;
2143
0
        decls << decl;
2144
0
        break;
2145
0
    case Html_em:
2146
0
    case Html_i:
2147
0
    case Html_cite:
2148
0
    case Html_address:
2149
0
    case Html_var:
2150
0
    case Html_dfn:
2151
0
        decl = QCss::Declaration();
2152
0
        decl.d->property = "font-style"_L1;
2153
0
        decl.d->propertyId = QCss::FontStyle;
2154
0
        val.type = QCss::Value::KnownIdentifier;
2155
0
        val.variant = QVariant(QCss::Value_Italic);
2156
0
        decl.d->values = QList<QCss::Value> { val };
2157
0
        decl.d->inheritable = true;
2158
0
        decls << decl;
2159
0
        break;
2160
0
    case Html_sub:
2161
0
    case Html_sup:
2162
0
        decl = QCss::Declaration();
2163
0
        decl.d->property = "vertical-align"_L1;
2164
0
        decl.d->propertyId = QCss::VerticalAlignment;
2165
0
        val.type = QCss::Value::KnownIdentifier;
2166
0
        val.variant = (node.id == Html_sub) ? QVariant(QCss::Value_Sub) : QVariant(QCss::Value_Super);
2167
0
        decl.d->values = QList<QCss::Value> { val };
2168
0
        decl.d->inheritable = true;
2169
0
        decls << decl;
2170
0
        break;
2171
0
    case Html_ul:
2172
0
    case Html_ol:
2173
0
        decl = QCss::Declaration();
2174
0
        decl.d->property = "list-style"_L1;
2175
0
        decl.d->propertyId = QCss::ListStyle;
2176
0
        val.type = QCss::Value::KnownIdentifier;
2177
0
        val.variant = (node.id == Html_ul) ? QVariant(QCss::Value_Disc) : QVariant(QCss::Value_Decimal);
2178
0
        decl.d->values = QList<QCss::Value> { val };
2179
0
        decl.d->inheritable = true;
2180
0
        decls << decl;
2181
0
        break;
2182
0
    case Html_code:
2183
0
    case Html_tt:
2184
0
    case Html_kbd:
2185
0
    case Html_samp:
2186
0
    case Html_pre: {
2187
0
        decl = QCss::Declaration();
2188
0
        decl.d->property = "font-family"_L1;
2189
0
        decl.d->propertyId = QCss::FontFamily;
2190
0
        QList<QCss::Value> values;
2191
0
        val.type = QCss::Value::String;
2192
0
        val.variant = QFontDatabase::systemFont(QFontDatabase::FixedFont).family();
2193
0
        values << val;
2194
0
        decl.d->values = values;
2195
0
        decl.d->inheritable = true;
2196
0
        decls << decl;
2197
0
        }
2198
0
        if (node.id != Html_pre)
2199
0
            break;
2200
0
        Q_FALLTHROUGH();
2201
0
    case Html_br:
2202
0
    case Html_nobr:
2203
0
        decl = QCss::Declaration();
2204
0
        decl.d->property = "whitespace"_L1;
2205
0
        decl.d->propertyId = QCss::Whitespace;
2206
0
        val.type = QCss::Value::KnownIdentifier;
2207
0
        switch (node.id) {
2208
0
        case Html_br: val.variant = QVariant(QCss::Value_PreWrap); break;
2209
0
        case Html_nobr: val.variant = QVariant(QCss::Value_NoWrap); break;
2210
0
        case Html_pre: val.variant = QVariant(QCss::Value_Pre); break;
2211
0
        default: break;
2212
0
        }
2213
0
        decl.d->values = QList<QCss::Value> { val };
2214
0
        decl.d->inheritable = true;
2215
0
        decls << decl;
2216
0
        break;
2217
0
    default:
2218
0
        break;
2219
0
    }
2220
0
    return decls;
2221
0
}
2222
2223
QList<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
2224
0
{
2225
0
    QList<QCss::Declaration> decls;
2226
2227
0
    QTextHtmlStyleSelector selector(this);
2228
2229
0
    int idx = 0;
2230
0
    selector.styleSheets.resize((resourceProvider ? 1 : 0)
2231
0
                                + externalStyleSheets.size()
2232
0
                                + inlineStyleSheets.size());
2233
0
    if (resourceProvider)
2234
0
        selector.styleSheets[idx++] = QTextDocumentPrivate::get(resourceProvider)->parsedDefaultStyleSheet;
2235
2236
0
    for (int i = 0; i < externalStyleSheets.size(); ++i, ++idx)
2237
0
        selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
2238
2239
0
    for (int i = 0; i < inlineStyleSheets.size(); ++i, ++idx)
2240
0
        selector.styleSheets[idx] = inlineStyleSheets.at(i);
2241
2242
0
    selector.medium = resourceProvider ? resourceProvider->metaInformation(QTextDocument::CssMedia) : "screen"_L1;
2243
2244
0
    QCss::StyleSelector::NodePtr n;
2245
0
    n.id = node;
2246
2247
0
    const char *extraPseudo = nullptr;
2248
0
    if (nodes.at(node)->id == Html_a && nodes.at(node)->hasHref)
2249
0
        extraPseudo = "link";
2250
    // Ensure that our own style is taken into consideration
2251
0
    decls = standardDeclarationForNode(*nodes.at(node));
2252
0
    decls += selector.declarationsForNode(n, extraPseudo);
2253
0
    n = selector.parentNode(n);
2254
0
    while (!selector.isNullNode(n)) {
2255
0
        QList<QCss::Declaration> inheritedDecls;
2256
0
        inheritedDecls = selector.declarationsForNode(n, extraPseudo);
2257
0
        for (int i = 0; i < inheritedDecls.size(); ++i) {
2258
0
            const QCss::Declaration &decl = inheritedDecls.at(i);
2259
0
            if (decl.d->inheritable)
2260
0
                decls.prepend(decl);
2261
0
        }
2262
0
        n = selector.parentNode(n);
2263
0
    }
2264
0
    return decls;
2265
0
}
2266
2267
bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
2268
0
{
2269
0
    while (i) {
2270
0
        if (at(i).id == id)
2271
0
            return true;
2272
0
        i = at(i).parent;
2273
0
    }
2274
0
    return false;
2275
0
}
2276
2277
#endif // QT_NO_CSSPARSER
2278
2279
QT_END_NAMESPACE
2280
2281
#endif // QT_NO_TEXTHTMLPARSER