source: trunk/src/gui/text/qtexthtmlparser.cpp@ 5

Last change on this file since 5 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 64.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtexthtmlparser_p.h"
43
44#include <qbytearray.h>
45#include <qtextcodec.h>
46#include <qapplication.h>
47#include <qstack.h>
48#include <qdebug.h>
49#include <qthread.h>
50
51#include "qtextdocument.h"
52#include "qtextformat_p.h"
53#include "qtextdocument_p.h"
54#include "qtextcursor.h"
55#include "qfont_p.h"
56#include "private/qunicodetables_p.h"
57#include "private/qfunctions_p.h"
58
59#ifndef QT_NO_TEXTHTMLPARSER
60
61QT_BEGIN_NAMESPACE
62
63// see also tst_qtextdocumentfragment.cpp
64#define MAX_ENTITY 258
65static const struct QTextHtmlEntity { const char *name; quint16 code; } entities[MAX_ENTITY]= {
66 { "AElig", 0x00c6 },
67 { "AMP", 38 },
68 { "Aacute", 0x00c1 },
69 { "Acirc", 0x00c2 },
70 { "Agrave", 0x00c0 },
71 { "Alpha", 0x0391 },
72 { "Aring", 0x00c5 },
73 { "Atilde", 0x00c3 },
74 { "Auml", 0x00c4 },
75 { "Beta", 0x0392 },
76 { "Ccedil", 0x00c7 },
77 { "Chi", 0x03a7 },
78 { "Dagger", 0x2021 },
79 { "Delta", 0x0394 },
80 { "ETH", 0x00d0 },
81 { "Eacute", 0x00c9 },
82 { "Ecirc", 0x00ca },
83 { "Egrave", 0x00c8 },
84 { "Epsilon", 0x0395 },
85 { "Eta", 0x0397 },
86 { "Euml", 0x00cb },
87 { "GT", 62 },
88 { "Gamma", 0x0393 },
89 { "Iacute", 0x00cd },
90 { "Icirc", 0x00ce },
91 { "Igrave", 0x00cc },
92 { "Iota", 0x0399 },
93 { "Iuml", 0x00cf },
94 { "Kappa", 0x039a },
95 { "LT", 60 },
96 { "Lambda", 0x039b },
97 { "Mu", 0x039c },
98 { "Ntilde", 0x00d1 },
99 { "Nu", 0x039d },
100 { "OElig", 0x0152 },
101 { "Oacute", 0x00d3 },
102 { "Ocirc", 0x00d4 },
103 { "Ograve", 0x00d2 },
104 { "Omega", 0x03a9 },
105 { "Omicron", 0x039f },
106 { "Oslash", 0x00d8 },
107 { "Otilde", 0x00d5 },
108 { "Ouml", 0x00d6 },
109 { "Phi", 0x03a6 },
110 { "Pi", 0x03a0 },
111 { "Prime", 0x2033 },
112 { "Psi", 0x03a8 },
113 { "QUOT", 34 },
114 { "Rho", 0x03a1 },
115 { "Scaron", 0x0160 },
116 { "Sigma", 0x03a3 },
117 { "THORN", 0x00de },
118 { "Tau", 0x03a4 },
119 { "Theta", 0x0398 },
120 { "Uacute", 0x00da },
121 { "Ucirc", 0x00db },
122 { "Ugrave", 0x00d9 },
123 { "Upsilon", 0x03a5 },
124 { "Uuml", 0x00dc },
125 { "Xi", 0x039e },
126 { "Yacute", 0x00dd },
127 { "Yuml", 0x0178 },
128 { "Zeta", 0x0396 },
129 { "aacute", 0x00e1 },
130 { "acirc", 0x00e2 },
131 { "acute", 0x00b4 },
132 { "aelig", 0x00e6 },
133 { "agrave", 0x00e0 },
134 { "alefsym", 0x2135 },
135 { "alpha", 0x03b1 },
136 { "amp", 38 },
137 { "and", 0x22a5 },
138 { "ang", 0x2220 },
139 { "apos", 0x0027 },
140 { "aring", 0x00e5 },
141 { "asymp", 0x2248 },
142 { "atilde", 0x00e3 },
143 { "auml", 0x00e4 },
144 { "bdquo", 0x201e },
145 { "beta", 0x03b2 },
146 { "brvbar", 0x00a6 },
147 { "bull", 0x2022 },
148 { "cap", 0x2229 },
149 { "ccedil", 0x00e7 },
150 { "cedil", 0x00b8 },
151 { "cent", 0x00a2 },
152 { "chi", 0x03c7 },
153 { "circ", 0x02c6 },
154 { "clubs", 0x2663 },
155 { "cong", 0x2245 },
156 { "copy", 0x00a9 },
157 { "crarr", 0x21b5 },
158 { "cup", 0x222a },
159 { "curren", 0x00a4 },
160 { "dArr", 0x21d3 },
161 { "dagger", 0x2020 },
162 { "darr", 0x2193 },
163 { "deg", 0x00b0 },
164 { "delta", 0x03b4 },
165 { "diams", 0x2666 },
166 { "divide", 0x00f7 },
167 { "eacute", 0x00e9 },
168 { "ecirc", 0x00ea },
169 { "egrave", 0x00e8 },
170 { "empty", 0x2205 },
171 { "emsp", 0x2003 },
172 { "ensp", 0x2002 },
173 { "epsilon", 0x03b5 },
174 { "equiv", 0x2261 },
175 { "eta", 0x03b7 },
176 { "eth", 0x00f0 },
177 { "euml", 0x00eb },
178 { "euro", 0x20ac },
179 { "exist", 0x2203 },
180 { "fnof", 0x0192 },
181 { "forall", 0x2200 },
182 { "frac12", 0x00bd },
183 { "frac14", 0x00bc },
184 { "frac34", 0x00be },
185 { "frasl", 0x2044 },
186 { "gamma", 0x03b3 },
187 { "ge", 0x2265 },
188 { "gt", 62 },
189 { "hArr", 0x21d4 },
190 { "harr", 0x2194 },
191 { "hearts", 0x2665 },
192 { "hellip", 0x2026 },
193 { "iacute", 0x00ed },
194 { "icirc", 0x00ee },
195 { "iexcl", 0x00a1 },
196 { "igrave", 0x00ec },
197 { "image", 0x2111 },
198 { "infin", 0x221e },
199 { "int", 0x222b },
200 { "iota", 0x03b9 },
201 { "iquest", 0x00bf },
202 { "isin", 0x2208 },
203 { "iuml", 0x00ef },
204 { "kappa", 0x03ba },
205 { "lArr", 0x21d0 },
206 { "lambda", 0x03bb },
207 { "lang", 0x2329 },
208 { "laquo", 0x00ab },
209 { "larr", 0x2190 },
210 { "lceil", 0x2308 },
211 { "ldquo", 0x201c },
212 { "le", 0x2264 },
213 { "lfloor", 0x230a },
214 { "lowast", 0x2217 },
215 { "loz", 0x25ca },
216 { "lrm", 0x200e },
217 { "lsaquo", 0x2039 },
218 { "lsquo", 0x2018 },
219 { "lt", 60 },
220 { "macr", 0x00af },
221 { "mdash", 0x2014 },
222 { "micro", 0x00b5 },
223 { "middot", 0x00b7 },
224 { "minus", 0x2212 },
225 { "mu", 0x03bc },
226 { "nabla", 0x2207 },
227 { "nbsp", 0x00a0 },
228 { "ndash", 0x2013 },
229 { "ne", 0x2260 },
230 { "ni", 0x220b },
231 { "not", 0x00ac },
232 { "notin", 0x2209 },
233 { "nsub", 0x2284 },
234 { "ntilde", 0x00f1 },
235 { "nu", 0x03bd },
236 { "oacute", 0x00f3 },
237 { "ocirc", 0x00f4 },
238 { "oelig", 0x0153 },
239 { "ograve", 0x00f2 },
240 { "oline", 0x203e },
241 { "omega", 0x03c9 },
242 { "omicron", 0x03bf },
243 { "oplus", 0x2295 },
244 { "or", 0x22a6 },
245 { "ordf", 0x00aa },
246 { "ordm", 0x00ba },
247 { "oslash", 0x00f8 },
248 { "otilde", 0x00f5 },
249 { "otimes", 0x2297 },
250 { "ouml", 0x00f6 },
251 { "para", 0x00b6 },
252 { "part", 0x2202 },
253 { "percnt", 0x0025 },
254 { "permil", 0x2030 },
255 { "perp", 0x22a5 },
256 { "phi", 0x03c6 },
257 { "pi", 0x03c0 },
258 { "piv", 0x03d6 },
259 { "plusmn", 0x00b1 },
260 { "pound", 0x00a3 },
261 { "prime", 0x2032 },
262 { "prod", 0x220f },
263 { "prop", 0x221d },
264 { "psi", 0x03c8 },
265 { "quot", 34 },
266 { "rArr", 0x21d2 },
267 { "radic", 0x221a },
268 { "rang", 0x232a },
269 { "raquo", 0x00bb },
270 { "rarr", 0x2192 },
271 { "rceil", 0x2309 },
272 { "rdquo", 0x201d },
273 { "real", 0x211c },
274 { "reg", 0x00ae },
275 { "rfloor", 0x230b },
276 { "rho", 0x03c1 },
277 { "rlm", 0x200f },
278 { "rsaquo", 0x203a },
279 { "rsquo", 0x2019 },
280 { "sbquo", 0x201a },
281 { "scaron", 0x0161 },
282 { "sdot", 0x22c5 },
283 { "sect", 0x00a7 },
284 { "shy", 0x00ad },
285 { "sigma", 0x03c3 },
286 { "sigmaf", 0x03c2 },
287 { "sim", 0x223c },
288 { "spades", 0x2660 },
289 { "sub", 0x2282 },
290 { "sube", 0x2286 },
291 { "sum", 0x2211 },
292 { "sup", 0x2283 },
293 { "sup1", 0x00b9 },
294 { "sup2", 0x00b2 },
295 { "sup3", 0x00b3 },
296 { "supe", 0x2287 },
297 { "szlig", 0x00df },
298 { "tau", 0x03c4 },
299 { "there4", 0x2234 },
300 { "theta", 0x03b8 },
301 { "thetasym", 0x03d1 },
302 { "thinsp", 0x2009 },
303 { "thorn", 0x00fe },
304 { "tilde", 0x02dc },
305 { "times", 0x00d7 },
306 { "trade", 0x2122 },
307 { "uArr", 0x21d1 },
308 { "uacute", 0x00fa },
309 { "uarr", 0x2191 },
310 { "ucirc", 0x00fb },
311 { "ugrave", 0x00f9 },
312 { "uml", 0x00a8 },
313 { "upsih", 0x03d2 },
314 { "upsilon", 0x03c5 },
315 { "uuml", 0x00fc },
316 { "weierp", 0x2118 },
317 { "xi", 0x03be },
318 { "yacute", 0x00fd },
319 { "yen", 0x00a5 },
320 { "yuml", 0x00ff },
321 { "zeta", 0x03b6 },
322 { "zwj", 0x200d },
323 { "zwnj", 0x200c }
324};
325
326Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &entityStr, const QTextHtmlEntity &entity)
327{
328 return entityStr < QLatin1String(entity.name);
329}
330
331Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlEntity &entity, const QString &entityStr)
332{
333 return QLatin1String(entity.name) < entityStr;
334}
335
336static QChar resolveEntity(const QString &entity)
337{
338 const QTextHtmlEntity *start = &entities[0];
339 const QTextHtmlEntity *end = &entities[MAX_ENTITY];
340 const QTextHtmlEntity *e = qBinaryFind(start, end, entity);
341 if (e == end)
342 return QChar();
343 return e->code;
344}
345
346static const uint windowsLatin1ExtendedCharacters[0xA0 - 0x80] = {
347 0x20ac, // 0x80
348 0x0081, // 0x81 direct mapping
349 0x201a, // 0x82
350 0x0192, // 0x83
351 0x201e, // 0x84
352 0x2026, // 0x85
353 0x2020, // 0x86
354 0x2021, // 0x87
355 0x02C6, // 0x88
356 0x2030, // 0x89
357 0x0160, // 0x8A
358 0x2039, // 0x8B
359 0x0152, // 0x8C
360 0x008D, // 0x8D direct mapping
361 0x017D, // 0x8E
362 0x008F, // 0x8F directmapping
363 0x0090, // 0x90 directmapping
364 0x2018, // 0x91
365 0x2019, // 0x92
366 0x201C, // 0x93
367 0X201D, // 0x94
368 0x2022, // 0x95
369 0x2013, // 0x96
370 0x2014, // 0x97
371 0x02DC, // 0x98
372 0x2122, // 0x99
373 0x0161, // 0x9A
374 0x203A, // 0x9B
375 0x0153, // 0x9C
376 0x009D, // 0x9D direct mapping
377 0x017E, // 0x9E
378 0x0178 // 0x9F
379};
380
381// the displayMode value is according to the what are blocks in the piecetable, not
382// what the w3c defines.
383static const QTextHtmlElement elements[Html_NumElements]= {
384 { "a", Html_a, QTextHtmlElement::DisplayInline },
385 { "address", Html_address, QTextHtmlElement::DisplayInline },
386 { "b", Html_b, QTextHtmlElement::DisplayInline },
387 { "big", Html_big, QTextHtmlElement::DisplayInline },
388 { "blockquote", Html_blockquote, QTextHtmlElement::DisplayBlock },
389 { "body", Html_body, QTextHtmlElement::DisplayBlock },
390 { "br", Html_br, QTextHtmlElement::DisplayInline },
391 { "caption", Html_caption, QTextHtmlElement::DisplayBlock },
392 { "center", Html_center, QTextHtmlElement::DisplayBlock },
393 { "cite", Html_cite, QTextHtmlElement::DisplayInline },
394 { "code", Html_code, QTextHtmlElement::DisplayInline },
395 { "dd", Html_dd, QTextHtmlElement::DisplayBlock },
396 { "dfn", Html_dfn, QTextHtmlElement::DisplayInline },
397 { "div", Html_div, QTextHtmlElement::DisplayBlock },
398 { "dl", Html_dl, QTextHtmlElement::DisplayBlock },
399 { "dt", Html_dt, QTextHtmlElement::DisplayBlock },
400 { "em", Html_em, QTextHtmlElement::DisplayInline },
401 { "font", Html_font, QTextHtmlElement::DisplayInline },
402 { "h1", Html_h1, QTextHtmlElement::DisplayBlock },
403 { "h2", Html_h2, QTextHtmlElement::DisplayBlock },
404 { "h3", Html_h3, QTextHtmlElement::DisplayBlock },
405 { "h4", Html_h4, QTextHtmlElement::DisplayBlock },
406 { "h5", Html_h5, QTextHtmlElement::DisplayBlock },
407 { "h6", Html_h6, QTextHtmlElement::DisplayBlock },
408 { "head", Html_head, QTextHtmlElement::DisplayNone },
409 { "hr", Html_hr, QTextHtmlElement::DisplayBlock },
410 { "html", Html_html, QTextHtmlElement::DisplayInline },
411 { "i", Html_i, QTextHtmlElement::DisplayInline },
412 { "img", Html_img, QTextHtmlElement::DisplayInline },
413 { "kbd", Html_kbd, QTextHtmlElement::DisplayInline },
414 { "li", Html_li, QTextHtmlElement::DisplayBlock },
415 { "link", Html_link, QTextHtmlElement::DisplayNone },
416 { "meta", Html_meta, QTextHtmlElement::DisplayNone },
417 { "nobr", Html_nobr, QTextHtmlElement::DisplayInline },
418 { "ol", Html_ol, QTextHtmlElement::DisplayBlock },
419 { "p", Html_p, QTextHtmlElement::DisplayBlock },
420 { "pre", Html_pre, QTextHtmlElement::DisplayBlock },
421 { "qt", Html_body /*deliberate mapping*/, QTextHtmlElement::DisplayBlock },
422 { "s", Html_s, QTextHtmlElement::DisplayInline },
423 { "samp", Html_samp, QTextHtmlElement::DisplayInline },
424 { "script", Html_script, QTextHtmlElement::DisplayNone },
425 { "small", Html_small, QTextHtmlElement::DisplayInline },
426 { "span", Html_span, QTextHtmlElement::DisplayInline },
427 { "strong", Html_strong, QTextHtmlElement::DisplayInline },
428 { "style", Html_style, QTextHtmlElement::DisplayNone },
429 { "sub", Html_sub, QTextHtmlElement::DisplayInline },
430 { "sup", Html_sup, QTextHtmlElement::DisplayInline },
431 { "table", Html_table, QTextHtmlElement::DisplayTable },
432 { "tbody", Html_tbody, QTextHtmlElement::DisplayTable },
433 { "td", Html_td, QTextHtmlElement::DisplayBlock },
434 { "tfoot", Html_tfoot, QTextHtmlElement::DisplayTable },
435 { "th", Html_th, QTextHtmlElement::DisplayBlock },
436 { "thead", Html_thead, QTextHtmlElement::DisplayTable },
437 { "title", Html_title, QTextHtmlElement::DisplayNone },
438 { "tr", Html_tr, QTextHtmlElement::DisplayTable },
439 { "tt", Html_tt, QTextHtmlElement::DisplayInline },
440 { "u", Html_u, QTextHtmlElement::DisplayInline },
441 { "ul", Html_ul, QTextHtmlElement::DisplayBlock },
442 { "var", Html_var, QTextHtmlElement::DisplayInline },
443};
444
445
446Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &str, const QTextHtmlElement &e)
447{
448 return str < QLatin1String(e.name);
449}
450
451Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlElement &e, const QString &str)
452{
453 return QLatin1String(e.name) < str;
454}
455
456static const QTextHtmlElement *lookupElementHelper(const QString &element)
457{
458 const QTextHtmlElement *start = &elements[0];
459 const QTextHtmlElement *end = &elements[Html_NumElements];
460 const QTextHtmlElement *e = qBinaryFind(start, end, element);
461 if (e == end)
462 return 0;
463 return e;
464}
465
466int QTextHtmlParser::lookupElement(const QString &element)
467{
468 const QTextHtmlElement *e = lookupElementHelper(element);
469 if (!e)
470 return -1;
471 return e->id;
472}
473
474// quotes newlines as "\\n"
475static QString quoteNewline(const QString &s)
476{
477 QString n = s;
478 n.replace(QLatin1Char('\n'), QLatin1String("\\n"));
479 return n;
480}
481
482QTextHtmlParserNode::QTextHtmlParserNode()
483 : parent(0), id(Html_unknown),
484 cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false),
485 hasCssListIndent(false), isEmptyParagraph(false), isTextFrame(false), isRootFrame(false),
486 displayMode(QTextHtmlElement::DisplayInline), hasHref(false),
487 listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
488 tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0),
489 borderBrush(Qt::darkGray), borderStyle(QTextFrameFormat::BorderStyle_Outset),
490 userState(-1), cssListIndent(0), wsm(WhiteSpaceModeUndefined)
491{
492 margin[QTextHtmlParser::MarginLeft] = 0;
493 margin[QTextHtmlParser::MarginRight] = 0;
494 margin[QTextHtmlParser::MarginTop] = 0;
495 margin[QTextHtmlParser::MarginBottom] = 0;
496}
497
498void QTextHtmlParser::dumpHtml()
499{
500 for (int i = 0; i < count(); ++i) {
501 qDebug().nospace() << qPrintable(QString(depth(i)*4, QLatin1Char(' ')))
502 << qPrintable(at(i).tag) << ":"
503 << quoteNewline(at(i).text);
504 ;
505 }
506}
507
508QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
509{
510 QTextHtmlParserNode *lastNode = &nodes.last();
511 QTextHtmlParserNode *newNode = 0;
512
513 bool reuseLastNode = true;
514
515 if (nodes.count() == 1) {
516 reuseLastNode = false;
517 } else if (lastNode->tag.isEmpty()) {
518
519 if (lastNode->text.isEmpty()) {
520 reuseLastNode = true;
521 } else { // last node is a text node (empty tag) with some text
522
523 if (lastNode->text.length() == 1 && lastNode->text.at(0).isSpace()) {
524
525 int lastSibling = count() - 2;
526 while (lastSibling
527 && at(lastSibling).parent != lastNode->parent
528 && at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
529 lastSibling = at(lastSibling).parent;
530 }
531
532 if (at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
533 reuseLastNode = false;
534 } else {
535 reuseLastNode = true;
536 }
537 } else {
538 // text node with real (non-whitespace) text -> nothing to re-use
539 reuseLastNode = false;
540 }
541
542 }
543
544 } else {
545 // last node had a proper tag -> nothing to re-use
546 reuseLastNode = false;
547 }
548
549 if (reuseLastNode) {
550 newNode = lastNode;
551 newNode->tag.clear();
552 newNode->text.clear();
553 newNode->id = Html_unknown;
554 } else {
555 nodes.resize(nodes.size() + 1);
556 newNode = &nodes.last();
557 }
558
559 newNode->parent = parent;
560 return newNode;
561}
562
563void QTextHtmlParser::parse(const QString &text, const QTextDocument *_resourceProvider)
564{
565 nodes.clear();
566 nodes.resize(1);
567 txt = text;
568 pos = 0;
569 len = txt.length();
570 textEditMode = false;
571 resourceProvider = _resourceProvider;
572 parse();
573 //dumpHtml();
574}
575
576int QTextHtmlParser::depth(int i) const
577{
578 int depth = 0;
579 while (i) {
580 i = at(i).parent;
581 ++depth;
582 }
583 return depth;
584}
585
586int QTextHtmlParser::margin(int i, int mar) const {
587 int m = 0;
588 const QTextHtmlParserNode *node;
589 if (mar == MarginLeft
590 || mar == MarginRight) {
591 while (i) {
592 node = &at(i);
593 if (!node->isBlock() && node->id != Html_table)
594 break;
595 if (node->isTableCell())
596 break;
597 m += node->margin[mar];
598 i = node->parent;
599 }
600 }
601 return m;
602}
603
604int QTextHtmlParser::topMargin(int i) const
605{
606 if (!i)
607 return 0;
608 return at(i).margin[MarginTop];
609}
610
611int QTextHtmlParser::bottomMargin(int i) const
612{
613 if (!i)
614 return 0;
615 return at(i).margin[MarginBottom];
616}
617
618void QTextHtmlParser::eatSpace()
619{
620 while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
621 pos++;
622}
623
624void QTextHtmlParser::parse()
625{
626 while (pos < len) {
627 QChar c = txt.at(pos++);
628 if (c == QLatin1Char('<')) {
629 parseTag();
630 } else if (c == QLatin1Char('&')) {
631 nodes.last().text += parseEntity();
632 } else {
633 nodes.last().text += c;
634 }
635 }
636}
637
638// parses a tag after "<"
639void QTextHtmlParser::parseTag()
640{
641 eatSpace();
642
643 // handle comments and other exclamation mark declarations
644 if (hasPrefix(QLatin1Char('!'))) {
645 parseExclamationTag();
646 if (nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePre
647 && nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePreWrap
648 && !textEditMode)
649 eatSpace();
650 return;
651 }
652
653 // if close tag just close
654 if (hasPrefix(QLatin1Char('/'))) {
655 if (nodes.last().id == Html_style) {
656#ifndef QT_NO_CSSPARSER
657 QCss::Parser parser(nodes.last().text);
658 QCss::StyleSheet sheet;
659 sheet.origin = QCss::StyleSheetOrigin_Author;
660 parser.parse(&sheet, Qt::CaseInsensitive);
661 inlineStyleSheets.append(sheet);
662 resolveStyleSheetImports(sheet);
663#endif
664 }
665 parseCloseTag();
666 return;
667 }
668
669 int p = last();
670 while (p && at(p).tag.size() == 0)
671 p = at(p).parent;
672
673 QTextHtmlParserNode *node = newNode(p);
674
675 // parse tag name
676 node->tag = parseWord().toLower();
677
678 const QTextHtmlElement *elem = lookupElementHelper(node->tag);
679 if (elem) {
680 node->id = elem->id;
681 node->displayMode = elem->displayMode;
682 } else {
683 node->id = Html_unknown;
684 }
685
686 node->attributes.clear();
687 // _need_ at least one space after the tag name, otherwise there can't be attributes
688 if (pos < len && txt.at(pos).isSpace())
689 node->attributes = parseAttributes();
690
691 // resolveParent() may have to change the order in the tree and
692 // insert intermediate nodes for buggy HTML, so re-initialize the 'node'
693 // pointer through the return value
694 node = resolveParent();
695 resolveNode();
696
697 const int nodeIndex = nodes.count() - 1; // this new node is always the last
698#ifndef QT_NO_CSSPARSER
699 node->applyCssDeclarations(declarationsForNode(nodeIndex), resourceProvider);
700#endif
701 applyAttributes(node->attributes);
702
703 // finish tag
704 bool tagClosed = false;
705 while (pos < len && txt.at(pos) != QLatin1Char('>')) {
706 if (txt.at(pos) == QLatin1Char('/'))
707 tagClosed = true;
708
709 pos++;
710 }
711 pos++;
712
713 // in a white-space preserving environment strip off a initial newline
714 // since the element itself already generates a newline
715 if ((node->wsm == QTextHtmlParserNode::WhiteSpacePre
716 || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
717 && node->isBlock()) {
718 if (pos < len - 1 && txt.at(pos) == QLatin1Char('\n'))
719 ++pos;
720 }
721
722 if (node->mayNotHaveChildren() || tagClosed) {
723 newNode(node->parent);
724 resolveNode();
725 }
726}
727
728// parses a tag beginning with "/"
729void QTextHtmlParser::parseCloseTag()
730{
731 ++pos;
732 QString tag = parseWord().toLower().trimmed();
733 while (pos < len) {
734 QChar c = txt.at(pos++);
735 if (c == QLatin1Char('>'))
736 break;
737 }
738
739 // find corresponding open node
740 int p = last();
741 if (p > 0
742 && at(p - 1).tag == tag
743 && at(p - 1).mayNotHaveChildren())
744 p--;
745
746 while (p && at(p).tag != tag)
747 p = at(p).parent;
748
749 // simply ignore the tag if we can't find
750 // a corresponding open node, for broken
751 // html such as <font>blah</font></font>
752 if (!p)
753 return;
754
755 // in a white-space preserving environment strip off a trailing newline
756 // since the closing of the opening block element will automatically result
757 // in a new block for elements following the <pre>
758 // ...foo\n</pre><p>blah -> foo</pre><p>blah
759 if ((at(p).wsm == QTextHtmlParserNode::WhiteSpacePre
760 || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
761 && at(p).isBlock()) {
762 if (at(last()).text.endsWith(QLatin1Char('\n')))
763 nodes[last()].text.chop(1);
764 }
765
766 newNode(at(p).parent);
767 resolveNode();
768}
769
770// parses a tag beginning with "!"
771void QTextHtmlParser::parseExclamationTag()
772{
773 ++pos;
774 if (hasPrefix(QLatin1Char('-'),1) && hasPrefix(QLatin1Char('-'),2)) {
775 pos += 3;
776 // eat comments
777 int end = txt.indexOf(QLatin1String("-->"), pos);
778 pos = (end >= 0 ? end + 3 : len);
779 } else {
780 // eat internal tags
781 while (pos < len) {
782 QChar c = txt.at(pos++);
783 if (c == QLatin1Char('>'))
784 break;
785 }
786 }
787}
788
789// parses an entity after "&", and returns it
790QString QTextHtmlParser::parseEntity()
791{
792 int recover = pos;
793 QString entity;
794 while (pos < len) {
795 QChar c = txt.at(pos++);
796 if (c.isSpace() || pos - recover > 9) {
797 goto error;
798 }
799 if (c == QLatin1Char(';'))
800 break;
801 entity += c;
802 }
803 {
804 QChar resolved = resolveEntity(entity);
805 if (!resolved.isNull())
806 return QString(resolved);
807 }
808 if (entity.length() > 1 && entity.at(0) == QLatin1Char('#')) {
809 entity.remove(0, 1); // removing leading #
810
811 int base = 10;
812 bool ok = false;
813
814 if (entity.at(0).toLower() == QLatin1Char('x')) { // hex entity?
815 entity.remove(0, 1);
816 base = 16;
817 }
818
819 uint uc = entity.toUInt(&ok, base);
820 if (ok) {
821 if (uc >= 0x80 && uc < 0x80 + (sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0])))
822 uc = windowsLatin1ExtendedCharacters[uc - 0x80];
823 QString str;
824 if (uc > 0xffff) {
825 // surrogate pair
826 uc -= 0x10000;
827 ushort high = uc/0x400 + 0xd800;
828 ushort low = uc%0x400 + 0xdc00;
829 str.append(QChar(high));
830 str.append(QChar(low));
831 } else {
832 str.append(QChar(uc));
833 }
834 return str;
835 }
836 }
837error:
838 pos = recover;
839 return QLatin1String("&");
840}
841
842// parses one word, possibly quoted, and returns it
843QString QTextHtmlParser::parseWord()
844{
845 QString word;
846 if (hasPrefix(QLatin1Char('\"'))) { // double quotes
847 ++pos;
848 while (pos < len) {
849 QChar c = txt.at(pos++);
850 if (c == QLatin1Char('\"'))
851 break;
852 else if (c == QLatin1Char('&'))
853 word += parseEntity();
854 else
855 word += c;
856 }
857 } else if (hasPrefix(QLatin1Char('\''))) { // single quotes
858 ++pos;
859 while (pos < len) {
860 QChar c = txt.at(pos++);
861 if (c == QLatin1Char('\''))
862 break;
863 else
864 word += c;
865 }
866 } else { // normal text
867 while (pos < len) {
868 QChar c = txt.at(pos++);
869 if (c == QLatin1Char('>')
870 || (c == QLatin1Char('/') && hasPrefix(QLatin1Char('>'), 1))
871 || c == QLatin1Char('<')
872 || c == QLatin1Char('=')
873 || c.isSpace()) {
874 --pos;
875 break;
876 }
877 if (c == QLatin1Char('&'))
878 word += parseEntity();
879 else
880 word += c;
881 }
882 }
883 return word;
884}
885
886// gives the new node the right parent
887QTextHtmlParserNode *QTextHtmlParser::resolveParent()
888{
889 QTextHtmlParserNode *node = &nodes.last();
890
891 int p = node->parent;
892
893 // Excel gives us buggy HTML with just tr without surrounding table tags
894 // or with just td tags
895
896 if (node->id == Html_td) {
897 int n = p;
898 while (n && at(n).id != Html_tr)
899 n = at(n).parent;
900
901 if (!n) {
902 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
903 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
904
905 QTextHtmlParserNode *table = &nodes[nodes.count() - 3];
906 table->parent = p;
907 table->id = Html_table;
908 table->tag = QLatin1String("table");
909 table->children.append(nodes.count() - 2); // add row as child
910
911 QTextHtmlParserNode *row = &nodes[nodes.count() - 2];
912 row->parent = nodes.count() - 3; // table as parent
913 row->id = Html_tr;
914 row->tag = QLatin1String("tr");
915
916 p = nodes.count() - 2;
917 node = &nodes.last(); // re-initialize pointer
918 }
919 }
920
921 if (node->id == Html_tr) {
922 int n = p;
923 while (n && at(n).id != Html_table)
924 n = at(n).parent;
925
926 if (!n) {
927 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
928 QTextHtmlParserNode *table = &nodes[nodes.count() - 2];
929 table->parent = p;
930 table->id = Html_table;
931 table->tag = QLatin1String("table");
932 p = nodes.count() - 2;
933 node = &nodes.last(); // re-initialize pointer
934 }
935 }
936
937 // permit invalid html by letting block elements be children
938 // of inline elements with the exception of paragraphs:
939 //
940 // a new paragraph closes parent inline elements (while loop),
941 // unless they themselves are children of a non-paragraph block
942 // element (if statement)
943 //
944 // For example:
945 //
946 // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
947 // belongs to the first <p>. The self-nesting
948 // check further down prevents the second <p>
949 // from nesting into the first one then.
950 // so Bar is not bold.
951 //
952 // <body><b><p>Foo <-- Foo should be bold.
953 //
954 // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
955 //
956 if (node->id == Html_p) {
957 while (p && !at(p).isBlock())
958 p = at(p).parent;
959
960 if (!p || at(p).id != Html_p)
961 p = node->parent;
962 }
963
964 // some elements are not self nesting
965 if (node->id == at(p).id
966 && node->isNotSelfNesting())
967 p = at(p).parent;
968
969 // some elements are not allowed in certain contexts
970 while ((p && !node->allowedInContext(at(p).id))
971 // ### make new styles aware of empty tags
972 || at(p).mayNotHaveChildren()
973 ) {
974 p = at(p).parent;
975 }
976
977 node->parent = p;
978
979 // makes it easier to traverse the tree, later
980 nodes[p].children.append(nodes.count() - 1);
981 return node;
982}
983
984// sets all properties on the new node
985void QTextHtmlParser::resolveNode()
986{
987 QTextHtmlParserNode *node = &nodes.last();
988 const QTextHtmlParserNode *parent = &nodes.at(node->parent);
989 node->initializeProperties(parent, this);
990}
991
992bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
993{
994 if (!isListStart())
995 return false;
996
997 int p = parent;
998 while (p) {
999 if (parser->at(p).isListStart())
1000 return true;
1001 p = parser->at(p).parent;
1002 }
1003 return false;
1004}
1005
1006void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
1007{
1008 // inherit properties from parent element
1009 charFormat = parent->charFormat;
1010
1011 if (id == Html_html)
1012 blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
1013 else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
1014 blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
1015
1016 if (parent->displayMode == QTextHtmlElement::DisplayNone)
1017 displayMode = QTextHtmlElement::DisplayNone;
1018
1019 if (parent->id != Html_table || id == Html_caption) {
1020 if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
1021 blockFormat.setAlignment(parent->blockFormat.alignment());
1022 else
1023 blockFormat.clearProperty(QTextFormat::BlockAlignment);
1024 }
1025 // we don't paint per-row background colors, yet. so as an
1026 // exception inherit the background color here
1027 // we also inherit the background between inline elements
1028 if ((parent->id != Html_tr || !isTableCell())
1029 && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)) {
1030 charFormat.clearProperty(QTextFormat::BackgroundBrush);
1031 }
1032
1033 listStyle = parent->listStyle;
1034 // makes no sense to inherit that property, a named anchor is a single point
1035 // in the document, which is set by the DocumentFragment
1036 charFormat.clearProperty(QTextFormat::AnchorName);
1037 wsm = parent->wsm;
1038
1039 // initialize remaining properties
1040 margin[QTextHtmlParser::MarginLeft] = 0;
1041 margin[QTextHtmlParser::MarginRight] = 0;
1042 margin[QTextHtmlParser::MarginTop] = 0;
1043 margin[QTextHtmlParser::MarginBottom] = 0;
1044 cssFloat = QTextFrameFormat::InFlow;
1045
1046 for (int i = 0; i < 4; ++i)
1047 padding[i] = -1;
1048
1049 // set element specific attributes
1050 switch (id) {
1051 case Html_a:
1052 charFormat.setAnchor(true);
1053 for (int i = 0; i < attributes.count(); i += 2) {
1054 const QString key = attributes.at(i);
1055 if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
1056 && !attributes.at(i + 1).isEmpty()) {
1057 hasHref = true;
1058 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1059 charFormat.setForeground(QApplication::palette().link());
1060 }
1061 }
1062
1063 break;
1064 case Html_em:
1065 case Html_i:
1066 case Html_cite:
1067 case Html_address:
1068 case Html_var:
1069 case Html_dfn:
1070 charFormat.setFontItalic(true);
1071 break;
1072 case Html_big:
1073 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1074 break;
1075 case Html_small:
1076 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1077 break;
1078 case Html_strong:
1079 case Html_b:
1080 charFormat.setFontWeight(QFont::Bold);
1081 break;
1082 case Html_h1:
1083 charFormat.setFontWeight(QFont::Bold);
1084 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
1085 margin[QTextHtmlParser::MarginTop] = 18;
1086 margin[QTextHtmlParser::MarginBottom] = 12;
1087 break;
1088 case Html_h2:
1089 charFormat.setFontWeight(QFont::Bold);
1090 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
1091 margin[QTextHtmlParser::MarginTop] = 16;
1092 margin[QTextHtmlParser::MarginBottom] = 12;
1093 break;
1094 case Html_h3:
1095 charFormat.setFontWeight(QFont::Bold);
1096 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1097 margin[QTextHtmlParser::MarginTop] = 14;
1098 margin[QTextHtmlParser::MarginBottom] = 12;
1099 break;
1100 case Html_h4:
1101 charFormat.setFontWeight(QFont::Bold);
1102 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
1103 margin[QTextHtmlParser::MarginTop] = 12;
1104 margin[QTextHtmlParser::MarginBottom] = 12;
1105 break;
1106 case Html_h5:
1107 charFormat.setFontWeight(QFont::Bold);
1108 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1109 margin[QTextHtmlParser::MarginTop] = 12;
1110 margin[QTextHtmlParser::MarginBottom] = 4;
1111 break;
1112 case Html_p:
1113 margin[QTextHtmlParser::MarginTop] = 12;
1114 margin[QTextHtmlParser::MarginBottom] = 12;
1115 break;
1116 case Html_center:
1117 blockFormat.setAlignment(Qt::AlignCenter);
1118 break;
1119 case Html_ul:
1120 listStyle = QTextListFormat::ListDisc;
1121 // nested lists don't have margins, except for the toplevel one
1122 if (!isNestedList(parser)) {
1123 margin[QTextHtmlParser::MarginTop] = 12;
1124 margin[QTextHtmlParser::MarginBottom] = 12;
1125 }
1126 // no left margin as we use indenting instead
1127 break;
1128 case Html_ol:
1129 listStyle = QTextListFormat::ListDecimal;
1130 // nested lists don't have margins, except for the toplevel one
1131 if (!isNestedList(parser)) {
1132 margin[QTextHtmlParser::MarginTop] = 12;
1133 margin[QTextHtmlParser::MarginBottom] = 12;
1134 }
1135 // no left margin as we use indenting instead
1136 break;
1137 case Html_code:
1138 case Html_tt:
1139 case Html_kbd:
1140 case Html_samp:
1141 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1142 // <tt> uses a fixed font, so set the property
1143 charFormat.setFontFixedPitch(true);
1144 break;
1145 case Html_br:
1146 text = QChar(QChar::LineSeparator);
1147 wsm = QTextHtmlParserNode::WhiteSpacePre;
1148 break;
1149 // ##### sub / sup
1150 case Html_pre:
1151 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1152 wsm = WhiteSpacePre;
1153 margin[QTextHtmlParser::MarginTop] = 12;
1154 margin[QTextHtmlParser::MarginBottom] = 12;
1155 // <pre> uses a fixed font
1156 charFormat.setFontFixedPitch(true);
1157 break;
1158 case Html_blockquote:
1159 margin[QTextHtmlParser::MarginTop] = 12;
1160 margin[QTextHtmlParser::MarginBottom] = 12;
1161 margin[QTextHtmlParser::MarginLeft] = 40;
1162 margin[QTextHtmlParser::MarginRight] = 40;
1163 break;
1164 case Html_dl:
1165 margin[QTextHtmlParser::MarginTop] = 8;
1166 margin[QTextHtmlParser::MarginBottom] = 8;
1167 break;
1168 case Html_dd:
1169 margin[QTextHtmlParser::MarginLeft] = 30;
1170 break;
1171 case Html_u:
1172 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1173 break;
1174 case Html_s:
1175 charFormat.setFontStrikeOut(true);
1176 break;
1177 case Html_nobr:
1178 wsm = WhiteSpaceNoWrap;
1179 break;
1180 case Html_th:
1181 charFormat.setFontWeight(QFont::Bold);
1182 blockFormat.setAlignment(Qt::AlignCenter);
1183 break;
1184 case Html_td:
1185 blockFormat.setAlignment(Qt::AlignLeft);
1186 break;
1187 case Html_sub:
1188 charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1189 break;
1190 case Html_sup:
1191 charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1192 break;
1193 default: break;
1194 }
1195}
1196
1197#ifndef QT_NO_CSSPARSER
1198void QTextHtmlParserNode::setListStyle(const QVector<QCss::Value> &cssValues)
1199{
1200 for (int i = 0; i < cssValues.count(); ++i) {
1201 if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
1202 switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
1203 case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
1204 case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
1205 case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
1206 case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
1207 case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
1208 case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
1209 default: break;
1210 }
1211 }
1212 }
1213 // allow individual list items to override the style
1214 if (id == Html_li && hasOwnListStyle)
1215 blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
1216}
1217
1218void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
1219{
1220 QCss::ValueExtractor extractor(declarations);
1221 extractor.extractBox(margin, padding);
1222
1223 for (int i = 0; i < declarations.count(); ++i) {
1224 const QCss::Declaration &decl = declarations.at(i);
1225 if (decl.d->values.isEmpty()) continue;
1226
1227 QCss::KnownValue identifier = QCss::UnknownValue;
1228 if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
1229 identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
1230
1231 switch (decl.d->propertyId) {
1232 case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
1233 case QCss::BorderStyles:
1234 if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
1235 borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
1236 break;
1237 case QCss::BorderWidth:
1238 tableBorder = extractor.lengthValue(decl);
1239 break;
1240 case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
1241 case QCss::Float:
1242 cssFloat = QTextFrameFormat::InFlow;
1243 switch (identifier) {
1244 case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
1245 case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
1246 default: break;
1247 }
1248 break;
1249 case QCss::QtBlockIndent:
1250 blockFormat.setIndent(decl.d->values.first().variant.toInt());
1251 break;
1252 case QCss::TextIndent: {
1253 qreal indent = 0;
1254 if (decl.realValue(&indent, "px"))
1255 blockFormat.setTextIndent(indent);
1256 break; }
1257 case QCss::QtListIndent:
1258 if (decl.intValue(&cssListIndent))
1259 hasCssListIndent = true;
1260 break;
1261 case QCss::QtParagraphType:
1262 if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
1263 isEmptyParagraph = true;
1264 break;
1265 case QCss::QtTableType:
1266 if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
1267 isTextFrame = true;
1268 else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
1269 isTextFrame = true;
1270 isRootFrame = true;
1271 }
1272 break;
1273 case QCss::QtUserState:
1274 userState = decl.d->values.first().variant.toInt();
1275 break;
1276 case QCss::Whitespace:
1277 switch (identifier) {
1278 case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
1279 case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
1280 case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
1281 case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
1282 default: break;
1283 }
1284 break;
1285 case QCss::VerticalAlignment:
1286 switch (identifier) {
1287 case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
1288 case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
1289 case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
1290 case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
1291 case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
1292 default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
1293 }
1294 break;
1295 case QCss::PageBreakBefore:
1296 switch (identifier) {
1297 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
1298 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
1299 default: break;
1300 }
1301 break;
1302 case QCss::PageBreakAfter:
1303 switch (identifier) {
1304 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
1305 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
1306 default: break;
1307 }
1308 break;
1309 case QCss::TextUnderlineStyle:
1310 switch (identifier) {
1311 case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
1312 case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
1313 case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
1314 case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
1315 case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
1316 case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
1317 case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
1318 default: break;
1319 }
1320 break;
1321 case QCss::ListStyleType:
1322 case QCss::ListStyle:
1323 setListStyle(decl.d->values);
1324 break;
1325 default: break;
1326 }
1327 }
1328
1329 QFont f;
1330 int adjustment = -255;
1331 extractor.extractFont(&f, &adjustment);
1332 if (f.resolve() & QFont::SizeResolved) {
1333 if (f.pointSize() > 0) {
1334 charFormat.setFontPointSize(f.pointSize());
1335 } else if (f.pixelSize() > 0) {
1336 charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
1337 }
1338 }
1339 if (f.resolve() & QFont::StyleResolved)
1340 charFormat.setFontItalic(f.style() != QFont::StyleNormal);
1341
1342 if (f.resolve() & QFont::WeightResolved)
1343 charFormat.setFontWeight(f.weight());
1344
1345 if (f.resolve() & QFont::FamilyResolved)
1346 charFormat.setFontFamily(f.family());
1347
1348 if (f.resolve() & QFont::UnderlineResolved)
1349 charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
1350
1351 if (f.resolve() & QFont::OverlineResolved)
1352 charFormat.setFontOverline(f.overline());
1353
1354 if (f.resolve() & QFont::StrikeOutResolved)
1355 charFormat.setFontStrikeOut(f.strikeOut());
1356
1357 if (f.resolve() & QFont::CapitalizationResolved)
1358 charFormat.setFontCapitalization(f.capitalization());
1359
1360 if (adjustment >= -1)
1361 charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
1362
1363 {
1364 Qt::Alignment ignoredAlignment;
1365 QCss::Repeat ignoredRepeat;
1366 QString bgImage;
1367 QBrush bgBrush;
1368 QCss::Origin ignoredOrigin, ignoredClip;
1369 QCss::Attachment ignoredAttachment;
1370 extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
1371 &ignoredOrigin, &ignoredAttachment, &ignoredClip);
1372
1373 if (!bgImage.isEmpty() && resourceProvider) {
1374 applyBackgroundImage(bgImage, resourceProvider);
1375 } else if (bgBrush.style() != Qt::NoBrush) {
1376 charFormat.setBackground(bgBrush);
1377 }
1378 }
1379}
1380
1381#endif // QT_NO_CSSPARSER
1382
1383void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
1384{
1385 if (!url.isEmpty() && resourceProvider) {
1386 QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
1387
1388 if (qApp->thread() != QThread::currentThread()) {
1389 // must use images in non-GUI threads
1390 if (val.type() == QVariant::Image) {
1391 QImage image = qvariant_cast<QImage>(val);
1392 charFormat.setBackground(image);
1393 } else if (val.type() == QVariant::ByteArray) {
1394 QImage image;
1395 if (image.loadFromData(val.toByteArray())) {
1396 charFormat.setBackground(image);
1397 }
1398 }
1399 } else {
1400 if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
1401 charFormat.setBackground(qvariant_cast<QPixmap>(val));
1402 } else if (val.type() == QVariant::ByteArray) {
1403 QPixmap pm;
1404 if (pm.loadFromData(val.toByteArray())) {
1405 charFormat.setBackground(pm);
1406 }
1407 }
1408 }
1409 }
1410 if (!url.isEmpty())
1411 charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
1412}
1413
1414bool QTextHtmlParserNode::hasOnlyWhitespace() const
1415{
1416 for (int i = 0; i < text.count(); ++i)
1417 if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
1418 return false;
1419 return true;
1420}
1421
1422static bool setIntAttribute(int *destination, const QString &value)
1423{
1424 bool ok = false;
1425 int val = value.toInt(&ok);
1426 if (ok)
1427 *destination = val;
1428
1429 return ok;
1430}
1431
1432static bool setFloatAttribute(qreal *destination, const QString &value)
1433{
1434 bool ok = false;
1435 qreal val = value.toDouble(&ok);
1436 if (ok)
1437 *destination = val;
1438
1439 return ok;
1440}
1441
1442static void setWidthAttribute(QTextLength *width, QString value)
1443{
1444 qreal realVal;
1445 bool ok = false;
1446 realVal = value.toDouble(&ok);
1447 if (ok) {
1448 *width = QTextLength(QTextLength::FixedLength, realVal);
1449 } else {
1450 value = value.trimmed();
1451 if (!value.isEmpty() && value.at(value.length() - 1) == QLatin1Char('%')) {
1452 value.chop(1);
1453 realVal = value.toDouble(&ok);
1454 if (ok)
1455 *width = QTextLength(QTextLength::PercentageLength, realVal);
1456 }
1457 }
1458}
1459
1460#ifndef QT_NO_CSSPARSER
1461void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
1462{
1463 QString css = value;
1464 css.prepend(QLatin1String("* {"));
1465 css.append(QLatin1Char('}'));
1466 QCss::Parser parser(css);
1467 QCss::StyleSheet sheet;
1468 parser.parse(&sheet, Qt::CaseInsensitive);
1469 if (sheet.styleRules.count() != 1) return;
1470 applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
1471}
1472#endif
1473
1474QStringList QTextHtmlParser::parseAttributes()
1475{
1476 QStringList attrs;
1477
1478 while (pos < len) {
1479 eatSpace();
1480 if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
1481 break;
1482 QString key = parseWord().toLower();
1483 QString value = QLatin1String("1");
1484 if (key.size() == 0)
1485 break;
1486 eatSpace();
1487 if (hasPrefix(QLatin1Char('='))){
1488 pos++;
1489 eatSpace();
1490 value = parseWord();
1491 }
1492 if (value.size() == 0)
1493 continue;
1494 attrs << key << value;
1495 }
1496
1497 return attrs;
1498}
1499
1500void QTextHtmlParser::applyAttributes(const QStringList &attributes)
1501{
1502 // local state variable for qt3 textedit mode
1503 bool seenQt3Richtext = false;
1504 QString linkHref;
1505 QString linkType;
1506
1507 if (attributes.count() % 2 == 1)
1508 return;
1509
1510 QTextHtmlParserNode *node = &nodes.last();
1511
1512 for (int i = 0; i < attributes.count(); i += 2) {
1513 QString key = attributes.at(i);
1514 QString value = attributes.at(i + 1);
1515
1516 switch (node->id) {
1517 case Html_font:
1518 // the infamous font tag
1519 if (key == QLatin1String("size") && value.size()) {
1520 int n = value.toInt();
1521 if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
1522 n -= 3;
1523 node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
1524 } else if (key == QLatin1String("face")) {
1525 node->charFormat.setFontFamily(value);
1526 } else if (key == QLatin1String("color")) {
1527 QColor c; c.setNamedColor(value);
1528 if (!c.isValid())
1529 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1530 node->charFormat.setForeground(c);
1531 }
1532 break;
1533 case Html_ol:
1534 case Html_ul:
1535 if (key == QLatin1String("type")) {
1536 node->hasOwnListStyle = true;
1537 if (value == QLatin1String("1")) {
1538 node->listStyle = QTextListFormat::ListDecimal;
1539 } else if (value == QLatin1String("a")) {
1540 node->listStyle = QTextListFormat::ListLowerAlpha;
1541 } else if (value == QLatin1String("A")) {
1542 node->listStyle = QTextListFormat::ListUpperAlpha;
1543 } else {
1544 value = value.toLower();
1545 if (value == QLatin1String("square"))
1546 node->listStyle = QTextListFormat::ListSquare;
1547 else if (value == QLatin1String("disc"))
1548 node->listStyle = QTextListFormat::ListDisc;
1549 else if (value == QLatin1String("circle"))
1550 node->listStyle = QTextListFormat::ListCircle;
1551 }
1552 }
1553 break;
1554 case Html_a:
1555 if (key == QLatin1String("href"))
1556 node->charFormat.setAnchorHref(value);
1557 else if (key == QLatin1String("name"))
1558 node->charFormat.setAnchorName(value);
1559 break;
1560 case Html_img:
1561 if (key == QLatin1String("src") || key == QLatin1String("source")) {
1562 node->imageName = value;
1563 } else if (key == QLatin1String("width")) {
1564 node->imageWidth = -2; // register that there is a value for it.
1565 setFloatAttribute(&node->imageWidth, value);
1566 } else if (key == QLatin1String("height")) {
1567 node->imageHeight = -2; // register that there is a value for it.
1568 setFloatAttribute(&node->imageHeight, value);
1569 }
1570 break;
1571 case Html_tr:
1572 case Html_body:
1573 if (key == QLatin1String("bgcolor")) {
1574 QColor c; c.setNamedColor(value);
1575 if (!c.isValid())
1576 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1577 node->charFormat.setBackground(c);
1578 } else if (key == QLatin1String("background")) {
1579 node->applyBackgroundImage(value, resourceProvider);
1580 }
1581 break;
1582 case Html_th:
1583 case Html_td:
1584 if (key == QLatin1String("width")) {
1585 setWidthAttribute(&node->width, value);
1586 } else if (key == QLatin1String("bgcolor")) {
1587 QColor c; c.setNamedColor(value);
1588 if (!c.isValid())
1589 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1590 node->charFormat.setBackground(c);
1591 } else if (key == QLatin1String("background")) {
1592 node->applyBackgroundImage(value, resourceProvider);
1593 } else if (key == QLatin1String("rowspan")) {
1594 if (setIntAttribute(&node->tableCellRowSpan, value))
1595 node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
1596 } else if (key == QLatin1String("colspan")) {
1597 if (setIntAttribute(&node->tableCellColSpan, value))
1598 node->tableCellColSpan = qMax(1, node->tableCellColSpan);
1599 }
1600 break;
1601 case Html_table:
1602 if (key == QLatin1String("border")) {
1603 setFloatAttribute(&node->tableBorder, value);
1604 } else if (key == QLatin1String("bgcolor")) {
1605 QColor c; c.setNamedColor(value);
1606 if (!c.isValid())
1607 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1608 node->charFormat.setBackground(c);
1609 } else if (key == QLatin1String("background")) {
1610 node->applyBackgroundImage(value, resourceProvider);
1611 } else if (key == QLatin1String("cellspacing")) {
1612 setFloatAttribute(&node->tableCellSpacing, value);
1613 } else if (key == QLatin1String("cellpadding")) {
1614 setFloatAttribute(&node->tableCellPadding, value);
1615 } else if (key == QLatin1String("width")) {
1616 setWidthAttribute(&node->width, value);
1617 } else if (key == QLatin1String("height")) {
1618 setWidthAttribute(&node->height, value);
1619 }
1620 break;
1621 case Html_meta:
1622 if (key == QLatin1String("name")
1623 && value == QLatin1String("qrichtext")) {
1624 seenQt3Richtext = true;
1625 }
1626
1627 if (key == QLatin1String("content")
1628 && value == QLatin1String("1")
1629 && seenQt3Richtext) {
1630
1631 textEditMode = true;
1632 }
1633 break;
1634 case Html_hr:
1635 if (key == QLatin1String("width"))
1636 setWidthAttribute(&node->width, value);
1637 break;
1638 case Html_link:
1639 if (key == QLatin1String("href"))
1640 linkHref = value;
1641 else if (key == QLatin1String("type"))
1642 linkType = value;
1643 break;
1644 default:
1645 break;
1646 }
1647
1648 if (key == QLatin1String("style")) {
1649#ifndef QT_NO_CSSPARSER
1650 node->parseStyleAttribute(value, resourceProvider);
1651#endif
1652 } else if (key == QLatin1String("align")) {
1653 value = value.toLower();
1654 bool alignmentSet = true;
1655
1656 if (value == QLatin1String("left"))
1657 node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
1658 else if (value == QLatin1String("right"))
1659 node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
1660 else if (value == QLatin1String("center"))
1661 node->blockFormat.setAlignment(Qt::AlignHCenter);
1662 else if (value == QLatin1String("justify"))
1663 node->blockFormat.setAlignment(Qt::AlignJustify);
1664 else
1665 alignmentSet = false;
1666
1667 if (node->id == Html_img) {
1668 // HTML4 compat
1669 if (alignmentSet) {
1670 if (node->blockFormat.alignment() & Qt::AlignLeft)
1671 node->cssFloat = QTextFrameFormat::FloatLeft;
1672 else if (node->blockFormat.alignment() & Qt::AlignRight)
1673 node->cssFloat = QTextFrameFormat::FloatRight;
1674 } else if (value == QLatin1String("middle")) {
1675 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1676 } else if (value == QLatin1String("top")) {
1677 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1678 }
1679 }
1680 } else if (key == QLatin1String("valign")) {
1681 value = value.toLower();
1682 if (value == QLatin1String("top"))
1683 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1684 else if (value == QLatin1String("middle"))
1685 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1686 else if (value == QLatin1String("bottom"))
1687 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
1688 } else if (key == QLatin1String("dir")) {
1689 value = value.toLower();
1690 if (value == QLatin1String("ltr"))
1691 node->blockFormat.setLayoutDirection(Qt::LeftToRight);
1692 else if (value == QLatin1String("rtl"))
1693 node->blockFormat.setLayoutDirection(Qt::RightToLeft);
1694 } else if (key == QLatin1String("title")) {
1695 node->charFormat.setToolTip(value);
1696 } else if (key == QLatin1String("id")) {
1697 node->charFormat.setAnchor(true);
1698 node->charFormat.setAnchorName(value);
1699 }
1700 }
1701
1702#ifndef QT_NO_CSSPARSER
1703 if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
1704 importStyleSheet(linkHref);
1705#endif
1706}
1707
1708#ifndef QT_NO_CSSPARSER
1709class QTextHtmlStyleSelector : public QCss::StyleSelector
1710{
1711public:
1712 inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
1713 : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
1714
1715 virtual QStringList nodeNames(NodePtr node) const;
1716 virtual QString attribute(NodePtr node, const QString &name) const;
1717 virtual bool hasAttributes(NodePtr node) const;
1718 virtual bool isNullNode(NodePtr node) const;
1719 virtual NodePtr parentNode(NodePtr node) const;
1720 virtual NodePtr previousSiblingNode(NodePtr node) const;
1721 virtual NodePtr duplicateNode(NodePtr node) const;
1722 virtual void freeNode(NodePtr node) const;
1723
1724private:
1725 const QTextHtmlParser *parser;
1726};
1727
1728QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
1729{
1730 return QStringList(parser->at(node.id).tag.toLower());
1731}
1732
1733#endif // QT_NO_CSSPARSER
1734
1735static inline int findAttribute(const QStringList &attributes, const QString &name)
1736{
1737 int idx = -1;
1738 do {
1739 idx = attributes.indexOf(name, idx + 1);
1740 } while (idx != -1 && (idx % 2 == 1));
1741 return idx;
1742}
1743
1744#ifndef QT_NO_CSSPARSER
1745
1746QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
1747{
1748 const QStringList &attributes = parser->at(node.id).attributes;
1749 const int idx = findAttribute(attributes, name);
1750 if (idx == -1)
1751 return QString();
1752 return attributes.at(idx + 1);
1753}
1754
1755bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
1756{
1757 const QStringList &attributes = parser->at(node.id).attributes;
1758 return !attributes.isEmpty();
1759}
1760
1761bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
1762{
1763 return node.id == 0;
1764}
1765
1766QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
1767{
1768 NodePtr parent;
1769 parent.id = 0;
1770 if (node.id) {
1771 parent.id = parser->at(node.id).parent;
1772 }
1773 return parent;
1774}
1775
1776QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
1777{
1778 return node;
1779}
1780
1781QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
1782{
1783 NodePtr sibling;
1784 sibling.id = 0;
1785 if (!node.id)
1786 return sibling;
1787 int parent = parser->at(node.id).parent;
1788 if (!parent)
1789 return sibling;
1790 const int childIdx = parser->at(parent).children.indexOf(node.id);
1791 if (childIdx <= 0)
1792 return sibling;
1793 sibling.id = parser->at(parent).children.at(childIdx - 1);
1794 return sibling;
1795}
1796
1797void QTextHtmlStyleSelector::freeNode(NodePtr) const
1798{
1799}
1800
1801void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
1802{
1803 for (int i = 0; i < sheet.importRules.count(); ++i) {
1804 const QCss::ImportRule &rule = sheet.importRules.at(i);
1805 if (rule.media.isEmpty()
1806 || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
1807 importStyleSheet(rule.href);
1808 }
1809}
1810
1811void QTextHtmlParser::importStyleSheet(const QString &href)
1812{
1813 if (!resourceProvider)
1814 return;
1815 for (int i = 0; i < externalStyleSheets.count(); ++i)
1816 if (externalStyleSheets.at(i).url == href)
1817 return;
1818
1819 QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
1820 QString css;
1821 if (res.type() == QVariant::String) {
1822 css = res.toString();
1823 } else if (res.type() == QVariant::ByteArray) {
1824 // #### detect @charset
1825 css = QString::fromUtf8(res.toByteArray());
1826 }
1827 if (!css.isEmpty()) {
1828 QCss::Parser parser(css);
1829 QCss::StyleSheet sheet;
1830 parser.parse(&sheet, Qt::CaseInsensitive);
1831 externalStyleSheets.append(ExternalStyleSheet(href, sheet));
1832 resolveStyleSheetImports(sheet);
1833 }
1834}
1835
1836QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
1837{
1838 QVector<QCss::Declaration> decls;
1839
1840 QTextHtmlStyleSelector selector(this);
1841
1842 int idx = 0;
1843 selector.styleSheets.resize((resourceProvider ? 1 : 0)
1844 + externalStyleSheets.count()
1845 + inlineStyleSheets.count());
1846 if (resourceProvider)
1847 selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
1848
1849 for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
1850 selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
1851
1852 for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
1853 selector.styleSheets[idx] = inlineStyleSheets.at(i);
1854
1855 selector.medium = QLatin1String("screen");
1856
1857 QCss::StyleSelector::NodePtr n;
1858 n.id = node;
1859
1860 const char *extraPseudo = 0;
1861 if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
1862 extraPseudo = "link";
1863 decls = selector.declarationsForNode(n, extraPseudo);
1864
1865 return decls;
1866}
1867
1868bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
1869{
1870 while (i) {
1871 if (at(i).id == id)
1872 return true;
1873 i = at(i).parent;
1874 }
1875 return false;
1876}
1877
1878QT_END_NAMESPACE
1879#endif // QT_NO_CSSPARSER
1880
1881#endif // QT_NO_TEXTHTMLPARSER
Note: See TracBrowser for help on using the repository browser.