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