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