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

Last change on this file since 563 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 64.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this 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 have questions regarding the use of this file, please contact
37** Nokia 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 ushort 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 case QCss::Value_LowerRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerRoman; break;
1210 case QCss::Value_UpperRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperRoman; break;
1211 default: break;
1212 }
1213 }
1214 }
1215 // allow individual list items to override the style
1216 if (id == Html_li && hasOwnListStyle)
1217 blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
1218}
1219
1220void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
1221{
1222 QCss::ValueExtractor extractor(declarations);
1223 extractor.extractBox(margin, padding);
1224
1225 for (int i = 0; i < declarations.count(); ++i) {
1226 const QCss::Declaration &decl = declarations.at(i);
1227 if (decl.d->values.isEmpty()) continue;
1228
1229 QCss::KnownValue identifier = QCss::UnknownValue;
1230 if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
1231 identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
1232
1233 switch (decl.d->propertyId) {
1234 case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
1235 case QCss::BorderStyles:
1236 if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
1237 borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
1238 break;
1239 case QCss::BorderWidth:
1240 tableBorder = extractor.lengthValue(decl);
1241 break;
1242 case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
1243 case QCss::Float:
1244 cssFloat = QTextFrameFormat::InFlow;
1245 switch (identifier) {
1246 case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
1247 case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
1248 default: break;
1249 }
1250 break;
1251 case QCss::QtBlockIndent:
1252 blockFormat.setIndent(decl.d->values.first().variant.toInt());
1253 break;
1254 case QCss::TextIndent: {
1255 qreal indent = 0;
1256 if (decl.realValue(&indent, "px"))
1257 blockFormat.setTextIndent(indent);
1258 break; }
1259 case QCss::QtListIndent:
1260 if (decl.intValue(&cssListIndent))
1261 hasCssListIndent = true;
1262 break;
1263 case QCss::QtParagraphType:
1264 if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
1265 isEmptyParagraph = true;
1266 break;
1267 case QCss::QtTableType:
1268 if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
1269 isTextFrame = true;
1270 else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
1271 isTextFrame = true;
1272 isRootFrame = true;
1273 }
1274 break;
1275 case QCss::QtUserState:
1276 userState = decl.d->values.first().variant.toInt();
1277 break;
1278 case QCss::Whitespace:
1279 switch (identifier) {
1280 case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
1281 case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
1282 case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
1283 case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
1284 default: break;
1285 }
1286 break;
1287 case QCss::VerticalAlignment:
1288 switch (identifier) {
1289 case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
1290 case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
1291 case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
1292 case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
1293 case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
1294 default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
1295 }
1296 break;
1297 case QCss::PageBreakBefore:
1298 switch (identifier) {
1299 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
1300 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
1301 default: break;
1302 }
1303 break;
1304 case QCss::PageBreakAfter:
1305 switch (identifier) {
1306 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
1307 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
1308 default: break;
1309 }
1310 break;
1311 case QCss::TextUnderlineStyle:
1312 switch (identifier) {
1313 case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
1314 case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
1315 case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
1316 case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
1317 case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
1318 case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
1319 case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
1320 default: break;
1321 }
1322 break;
1323 case QCss::ListStyleType:
1324 case QCss::ListStyle:
1325 setListStyle(decl.d->values);
1326 break;
1327 default: break;
1328 }
1329 }
1330
1331 QFont f;
1332 int adjustment = -255;
1333 extractor.extractFont(&f, &adjustment);
1334 if (f.resolve() & QFont::SizeResolved) {
1335 if (f.pointSize() > 0) {
1336 charFormat.setFontPointSize(f.pointSize());
1337 } else if (f.pixelSize() > 0) {
1338 charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
1339 }
1340 }
1341 if (f.resolve() & QFont::StyleResolved)
1342 charFormat.setFontItalic(f.style() != QFont::StyleNormal);
1343
1344 if (f.resolve() & QFont::WeightResolved)
1345 charFormat.setFontWeight(f.weight());
1346
1347 if (f.resolve() & QFont::FamilyResolved)
1348 charFormat.setFontFamily(f.family());
1349
1350 if (f.resolve() & QFont::UnderlineResolved)
1351 charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
1352
1353 if (f.resolve() & QFont::OverlineResolved)
1354 charFormat.setFontOverline(f.overline());
1355
1356 if (f.resolve() & QFont::StrikeOutResolved)
1357 charFormat.setFontStrikeOut(f.strikeOut());
1358
1359 if (f.resolve() & QFont::CapitalizationResolved)
1360 charFormat.setFontCapitalization(f.capitalization());
1361
1362 if (adjustment >= -1)
1363 charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
1364
1365 {
1366 Qt::Alignment ignoredAlignment;
1367 QCss::Repeat ignoredRepeat;
1368 QString bgImage;
1369 QBrush bgBrush;
1370 QCss::Origin ignoredOrigin, ignoredClip;
1371 QCss::Attachment ignoredAttachment;
1372 extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
1373 &ignoredOrigin, &ignoredAttachment, &ignoredClip);
1374
1375 if (!bgImage.isEmpty() && resourceProvider) {
1376 applyBackgroundImage(bgImage, resourceProvider);
1377 } else if (bgBrush.style() != Qt::NoBrush) {
1378 charFormat.setBackground(bgBrush);
1379 }
1380 }
1381}
1382
1383#endif // QT_NO_CSSPARSER
1384
1385void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
1386{
1387 if (!url.isEmpty() && resourceProvider) {
1388 QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
1389
1390 if (qApp->thread() != QThread::currentThread()) {
1391 // must use images in non-GUI threads
1392 if (val.type() == QVariant::Image) {
1393 QImage image = qvariant_cast<QImage>(val);
1394 charFormat.setBackground(image);
1395 } else if (val.type() == QVariant::ByteArray) {
1396 QImage image;
1397 if (image.loadFromData(val.toByteArray())) {
1398 charFormat.setBackground(image);
1399 }
1400 }
1401 } else {
1402 if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
1403 charFormat.setBackground(qvariant_cast<QPixmap>(val));
1404 } else if (val.type() == QVariant::ByteArray) {
1405 QPixmap pm;
1406 if (pm.loadFromData(val.toByteArray())) {
1407 charFormat.setBackground(pm);
1408 }
1409 }
1410 }
1411 }
1412 if (!url.isEmpty())
1413 charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
1414}
1415
1416bool QTextHtmlParserNode::hasOnlyWhitespace() const
1417{
1418 for (int i = 0; i < text.count(); ++i)
1419 if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
1420 return false;
1421 return true;
1422}
1423
1424static bool setIntAttribute(int *destination, const QString &value)
1425{
1426 bool ok = false;
1427 int val = value.toInt(&ok);
1428 if (ok)
1429 *destination = val;
1430
1431 return ok;
1432}
1433
1434static bool setFloatAttribute(qreal *destination, const QString &value)
1435{
1436 bool ok = false;
1437 qreal val = value.toDouble(&ok);
1438 if (ok)
1439 *destination = val;
1440
1441 return ok;
1442}
1443
1444static void setWidthAttribute(QTextLength *width, QString value)
1445{
1446 bool ok = false;
1447 qreal realVal = value.toDouble(&ok);
1448 if (ok) {
1449 *width = QTextLength(QTextLength::FixedLength, realVal);
1450 } else {
1451 value = value.trimmed();
1452 if (!value.isEmpty() && value.endsWith(QLatin1Char('%'))) {
1453 value.chop(1);
1454 realVal = value.toDouble(&ok);
1455 if (ok)
1456 *width = QTextLength(QTextLength::PercentageLength, realVal);
1457 }
1458 }
1459}
1460
1461#ifndef QT_NO_CSSPARSER
1462void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
1463{
1464 QString css = value;
1465 css.prepend(QLatin1String("* {"));
1466 css.append(QLatin1Char('}'));
1467 QCss::Parser parser(css);
1468 QCss::StyleSheet sheet;
1469 parser.parse(&sheet, Qt::CaseInsensitive);
1470 if (sheet.styleRules.count() != 1) return;
1471 applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
1472}
1473#endif
1474
1475QStringList QTextHtmlParser::parseAttributes()
1476{
1477 QStringList attrs;
1478
1479 while (pos < len) {
1480 eatSpace();
1481 if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
1482 break;
1483 QString key = parseWord().toLower();
1484 QString value = QLatin1String("1");
1485 if (key.size() == 0)
1486 break;
1487 eatSpace();
1488 if (hasPrefix(QLatin1Char('='))){
1489 pos++;
1490 eatSpace();
1491 value = parseWord();
1492 }
1493 if (value.size() == 0)
1494 continue;
1495 attrs << key << value;
1496 }
1497
1498 return attrs;
1499}
1500
1501void QTextHtmlParser::applyAttributes(const QStringList &attributes)
1502{
1503 // local state variable for qt3 textedit mode
1504 bool seenQt3Richtext = false;
1505 QString linkHref;
1506 QString linkType;
1507
1508 if (attributes.count() % 2 == 1)
1509 return;
1510
1511 QTextHtmlParserNode *node = &nodes.last();
1512
1513 for (int i = 0; i < attributes.count(); i += 2) {
1514 QString key = attributes.at(i);
1515 QString value = attributes.at(i + 1);
1516
1517 switch (node->id) {
1518 case Html_font:
1519 // the infamous font tag
1520 if (key == QLatin1String("size") && value.size()) {
1521 int n = value.toInt();
1522 if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
1523 n -= 3;
1524 node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
1525 } else if (key == QLatin1String("face")) {
1526 node->charFormat.setFontFamily(value);
1527 } else if (key == QLatin1String("color")) {
1528 QColor c; c.setNamedColor(value);
1529 if (!c.isValid())
1530 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1531 node->charFormat.setForeground(c);
1532 }
1533 break;
1534 case Html_ol:
1535 case Html_ul:
1536 if (key == QLatin1String("type")) {
1537 node->hasOwnListStyle = true;
1538 if (value == QLatin1String("1")) {
1539 node->listStyle = QTextListFormat::ListDecimal;
1540 } else if (value == QLatin1String("a")) {
1541 node->listStyle = QTextListFormat::ListLowerAlpha;
1542 } else if (value == QLatin1String("A")) {
1543 node->listStyle = QTextListFormat::ListUpperAlpha;
1544 } else if (value == QLatin1String("i")) {
1545 node->listStyle = QTextListFormat::ListLowerRoman;
1546 } else if (value == QLatin1String("I")) {
1547 node->listStyle = QTextListFormat::ListUpperRoman;
1548 } else {
1549 value = value.toLower();
1550 if (value == QLatin1String("square"))
1551 node->listStyle = QTextListFormat::ListSquare;
1552 else if (value == QLatin1String("disc"))
1553 node->listStyle = QTextListFormat::ListDisc;
1554 else if (value == QLatin1String("circle"))
1555 node->listStyle = QTextListFormat::ListCircle;
1556 }
1557 }
1558 break;
1559 case Html_a:
1560 if (key == QLatin1String("href"))
1561 node->charFormat.setAnchorHref(value);
1562 else if (key == QLatin1String("name"))
1563 node->charFormat.setAnchorName(value);
1564 break;
1565 case Html_img:
1566 if (key == QLatin1String("src") || key == QLatin1String("source")) {
1567 node->imageName = value;
1568 } else if (key == QLatin1String("width")) {
1569 node->imageWidth = -2; // register that there is a value for it.
1570 setFloatAttribute(&node->imageWidth, value);
1571 } else if (key == QLatin1String("height")) {
1572 node->imageHeight = -2; // register that there is a value for it.
1573 setFloatAttribute(&node->imageHeight, value);
1574 }
1575 break;
1576 case Html_tr:
1577 case Html_body:
1578 if (key == QLatin1String("bgcolor")) {
1579 QColor c; c.setNamedColor(value);
1580 if (!c.isValid())
1581 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1582 node->charFormat.setBackground(c);
1583 } else if (key == QLatin1String("background")) {
1584 node->applyBackgroundImage(value, resourceProvider);
1585 }
1586 break;
1587 case Html_th:
1588 case Html_td:
1589 if (key == QLatin1String("width")) {
1590 setWidthAttribute(&node->width, value);
1591 } else if (key == QLatin1String("bgcolor")) {
1592 QColor c; c.setNamedColor(value);
1593 if (!c.isValid())
1594 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1595 node->charFormat.setBackground(c);
1596 } else if (key == QLatin1String("background")) {
1597 node->applyBackgroundImage(value, resourceProvider);
1598 } else if (key == QLatin1String("rowspan")) {
1599 if (setIntAttribute(&node->tableCellRowSpan, value))
1600 node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
1601 } else if (key == QLatin1String("colspan")) {
1602 if (setIntAttribute(&node->tableCellColSpan, value))
1603 node->tableCellColSpan = qMax(1, node->tableCellColSpan);
1604 }
1605 break;
1606 case Html_table:
1607 if (key == QLatin1String("border")) {
1608 setFloatAttribute(&node->tableBorder, value);
1609 } else if (key == QLatin1String("bgcolor")) {
1610 QColor c; c.setNamedColor(value);
1611 if (!c.isValid())
1612 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1613 node->charFormat.setBackground(c);
1614 } else if (key == QLatin1String("background")) {
1615 node->applyBackgroundImage(value, resourceProvider);
1616 } else if (key == QLatin1String("cellspacing")) {
1617 setFloatAttribute(&node->tableCellSpacing, value);
1618 } else if (key == QLatin1String("cellpadding")) {
1619 setFloatAttribute(&node->tableCellPadding, value);
1620 } else if (key == QLatin1String("width")) {
1621 setWidthAttribute(&node->width, value);
1622 } else if (key == QLatin1String("height")) {
1623 setWidthAttribute(&node->height, value);
1624 }
1625 break;
1626 case Html_meta:
1627 if (key == QLatin1String("name")
1628 && value == QLatin1String("qrichtext")) {
1629 seenQt3Richtext = true;
1630 }
1631
1632 if (key == QLatin1String("content")
1633 && value == QLatin1String("1")
1634 && seenQt3Richtext) {
1635
1636 textEditMode = true;
1637 }
1638 break;
1639 case Html_hr:
1640 if (key == QLatin1String("width"))
1641 setWidthAttribute(&node->width, value);
1642 break;
1643 case Html_link:
1644 if (key == QLatin1String("href"))
1645 linkHref = value;
1646 else if (key == QLatin1String("type"))
1647 linkType = value;
1648 break;
1649 default:
1650 break;
1651 }
1652
1653 if (key == QLatin1String("style")) {
1654#ifndef QT_NO_CSSPARSER
1655 node->parseStyleAttribute(value, resourceProvider);
1656#endif
1657 } else if (key == QLatin1String("align")) {
1658 value = value.toLower();
1659 bool alignmentSet = true;
1660
1661 if (value == QLatin1String("left"))
1662 node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
1663 else if (value == QLatin1String("right"))
1664 node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
1665 else if (value == QLatin1String("center"))
1666 node->blockFormat.setAlignment(Qt::AlignHCenter);
1667 else if (value == QLatin1String("justify"))
1668 node->blockFormat.setAlignment(Qt::AlignJustify);
1669 else
1670 alignmentSet = false;
1671
1672 if (node->id == Html_img) {
1673 // HTML4 compat
1674 if (alignmentSet) {
1675 if (node->blockFormat.alignment() & Qt::AlignLeft)
1676 node->cssFloat = QTextFrameFormat::FloatLeft;
1677 else if (node->blockFormat.alignment() & Qt::AlignRight)
1678 node->cssFloat = QTextFrameFormat::FloatRight;
1679 } else if (value == QLatin1String("middle")) {
1680 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1681 } else if (value == QLatin1String("top")) {
1682 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1683 }
1684 }
1685 } else if (key == QLatin1String("valign")) {
1686 value = value.toLower();
1687 if (value == QLatin1String("top"))
1688 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1689 else if (value == QLatin1String("middle"))
1690 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1691 else if (value == QLatin1String("bottom"))
1692 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
1693 } else if (key == QLatin1String("dir")) {
1694 value = value.toLower();
1695 if (value == QLatin1String("ltr"))
1696 node->blockFormat.setLayoutDirection(Qt::LeftToRight);
1697 else if (value == QLatin1String("rtl"))
1698 node->blockFormat.setLayoutDirection(Qt::RightToLeft);
1699 } else if (key == QLatin1String("title")) {
1700 node->charFormat.setToolTip(value);
1701 } else if (key == QLatin1String("id")) {
1702 node->charFormat.setAnchor(true);
1703 node->charFormat.setAnchorName(value);
1704 }
1705 }
1706
1707#ifndef QT_NO_CSSPARSER
1708 if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
1709 importStyleSheet(linkHref);
1710#endif
1711}
1712
1713#ifndef QT_NO_CSSPARSER
1714class QTextHtmlStyleSelector : public QCss::StyleSelector
1715{
1716public:
1717 inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
1718 : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
1719
1720 virtual QStringList nodeNames(NodePtr node) const;
1721 virtual QString attribute(NodePtr node, const QString &name) const;
1722 virtual bool hasAttributes(NodePtr node) const;
1723 virtual bool isNullNode(NodePtr node) const;
1724 virtual NodePtr parentNode(NodePtr node) const;
1725 virtual NodePtr previousSiblingNode(NodePtr node) const;
1726 virtual NodePtr duplicateNode(NodePtr node) const;
1727 virtual void freeNode(NodePtr node) const;
1728
1729private:
1730 const QTextHtmlParser *parser;
1731};
1732
1733QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
1734{
1735 return QStringList(parser->at(node.id).tag.toLower());
1736}
1737
1738#endif // QT_NO_CSSPARSER
1739
1740static inline int findAttribute(const QStringList &attributes, const QString &name)
1741{
1742 int idx = -1;
1743 do {
1744 idx = attributes.indexOf(name, idx + 1);
1745 } while (idx != -1 && (idx % 2 == 1));
1746 return idx;
1747}
1748
1749#ifndef QT_NO_CSSPARSER
1750
1751QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
1752{
1753 const QStringList &attributes = parser->at(node.id).attributes;
1754 const int idx = findAttribute(attributes, name);
1755 if (idx == -1)
1756 return QString();
1757 return attributes.at(idx + 1);
1758}
1759
1760bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
1761{
1762 const QStringList &attributes = parser->at(node.id).attributes;
1763 return !attributes.isEmpty();
1764}
1765
1766bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
1767{
1768 return node.id == 0;
1769}
1770
1771QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
1772{
1773 NodePtr parent;
1774 parent.id = 0;
1775 if (node.id) {
1776 parent.id = parser->at(node.id).parent;
1777 }
1778 return parent;
1779}
1780
1781QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
1782{
1783 return node;
1784}
1785
1786QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
1787{
1788 NodePtr sibling;
1789 sibling.id = 0;
1790 if (!node.id)
1791 return sibling;
1792 int parent = parser->at(node.id).parent;
1793 if (!parent)
1794 return sibling;
1795 const int childIdx = parser->at(parent).children.indexOf(node.id);
1796 if (childIdx <= 0)
1797 return sibling;
1798 sibling.id = parser->at(parent).children.at(childIdx - 1);
1799 return sibling;
1800}
1801
1802void QTextHtmlStyleSelector::freeNode(NodePtr) const
1803{
1804}
1805
1806void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
1807{
1808 for (int i = 0; i < sheet.importRules.count(); ++i) {
1809 const QCss::ImportRule &rule = sheet.importRules.at(i);
1810 if (rule.media.isEmpty()
1811 || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
1812 importStyleSheet(rule.href);
1813 }
1814}
1815
1816void QTextHtmlParser::importStyleSheet(const QString &href)
1817{
1818 if (!resourceProvider)
1819 return;
1820 for (int i = 0; i < externalStyleSheets.count(); ++i)
1821 if (externalStyleSheets.at(i).url == href)
1822 return;
1823
1824 QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
1825 QString css;
1826 if (res.type() == QVariant::String) {
1827 css = res.toString();
1828 } else if (res.type() == QVariant::ByteArray) {
1829 // #### detect @charset
1830 css = QString::fromUtf8(res.toByteArray());
1831 }
1832 if (!css.isEmpty()) {
1833 QCss::Parser parser(css);
1834 QCss::StyleSheet sheet;
1835 parser.parse(&sheet, Qt::CaseInsensitive);
1836 externalStyleSheets.append(ExternalStyleSheet(href, sheet));
1837 resolveStyleSheetImports(sheet);
1838 }
1839}
1840
1841QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
1842{
1843 QVector<QCss::Declaration> decls;
1844
1845 QTextHtmlStyleSelector selector(this);
1846
1847 int idx = 0;
1848 selector.styleSheets.resize((resourceProvider ? 1 : 0)
1849 + externalStyleSheets.count()
1850 + inlineStyleSheets.count());
1851 if (resourceProvider)
1852 selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
1853
1854 for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
1855 selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
1856
1857 for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
1858 selector.styleSheets[idx] = inlineStyleSheets.at(i);
1859
1860 selector.medium = QLatin1String("screen");
1861
1862 QCss::StyleSelector::NodePtr n;
1863 n.id = node;
1864
1865 const char *extraPseudo = 0;
1866 if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
1867 extraPseudo = "link";
1868 decls = selector.declarationsForNode(n, extraPseudo);
1869
1870 return decls;
1871}
1872
1873bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
1874{
1875 while (i) {
1876 if (at(i).id == id)
1877 return true;
1878 i = at(i).parent;
1879 }
1880 return false;
1881}
1882
1883QT_END_NAMESPACE
1884#endif // QT_NO_CSSPARSER
1885
1886#endif // QT_NO_TEXTHTMLPARSER
Note: See TracBrowser for help on using the repository browser.