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