summaryrefslogtreecommitdiffstats
path: root/src/gui/text/qtexthtmlparser.cpp
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/gui/text/qtexthtmlparser.cpp
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/gui/text/qtexthtmlparser.cpp')
-rw-r--r--src/gui/text/qtexthtmlparser.cpp1905
1 files changed, 1905 insertions, 0 deletions
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
new file mode 100644
index 0000000000..3513a280c6
--- /dev/null
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -0,0 +1,1905 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtexthtmlparser_p.h"
+
+#include <qbytearray.h>
+#include <qtextcodec.h>
+#include <qapplication.h>
+#include <qstack.h>
+#include <qdebug.h>
+#include <qthread.h>
+
+#include "qtextdocument.h"
+#include "qtextformat_p.h"
+#include "qtextdocument_p.h"
+#include "qtextcursor.h"
+#include "qfont_p.h"
+#include "private/qfunctions_p.h"
+
+#ifndef QT_NO_TEXTHTMLPARSER
+
+QT_BEGIN_NAMESPACE
+
+// see also tst_qtextdocumentfragment.cpp
+#define MAX_ENTITY 258
+static const struct QTextHtmlEntity { const char *name; quint16 code; } entities[MAX_ENTITY]= {
+ { "AElig", 0x00c6 },
+ { "AMP", 38 },
+ { "Aacute", 0x00c1 },
+ { "Acirc", 0x00c2 },
+ { "Agrave", 0x00c0 },
+ { "Alpha", 0x0391 },
+ { "Aring", 0x00c5 },
+ { "Atilde", 0x00c3 },
+ { "Auml", 0x00c4 },
+ { "Beta", 0x0392 },
+ { "Ccedil", 0x00c7 },
+ { "Chi", 0x03a7 },
+ { "Dagger", 0x2021 },
+ { "Delta", 0x0394 },
+ { "ETH", 0x00d0 },
+ { "Eacute", 0x00c9 },
+ { "Ecirc", 0x00ca },
+ { "Egrave", 0x00c8 },
+ { "Epsilon", 0x0395 },
+ { "Eta", 0x0397 },
+ { "Euml", 0x00cb },
+ { "GT", 62 },
+ { "Gamma", 0x0393 },
+ { "Iacute", 0x00cd },
+ { "Icirc", 0x00ce },
+ { "Igrave", 0x00cc },
+ { "Iota", 0x0399 },
+ { "Iuml", 0x00cf },
+ { "Kappa", 0x039a },
+ { "LT", 60 },
+ { "Lambda", 0x039b },
+ { "Mu", 0x039c },
+ { "Ntilde", 0x00d1 },
+ { "Nu", 0x039d },
+ { "OElig", 0x0152 },
+ { "Oacute", 0x00d3 },
+ { "Ocirc", 0x00d4 },
+ { "Ograve", 0x00d2 },
+ { "Omega", 0x03a9 },
+ { "Omicron", 0x039f },
+ { "Oslash", 0x00d8 },
+ { "Otilde", 0x00d5 },
+ { "Ouml", 0x00d6 },
+ { "Phi", 0x03a6 },
+ { "Pi", 0x03a0 },
+ { "Prime", 0x2033 },
+ { "Psi", 0x03a8 },
+ { "QUOT", 34 },
+ { "Rho", 0x03a1 },
+ { "Scaron", 0x0160 },
+ { "Sigma", 0x03a3 },
+ { "THORN", 0x00de },
+ { "Tau", 0x03a4 },
+ { "Theta", 0x0398 },
+ { "Uacute", 0x00da },
+ { "Ucirc", 0x00db },
+ { "Ugrave", 0x00d9 },
+ { "Upsilon", 0x03a5 },
+ { "Uuml", 0x00dc },
+ { "Xi", 0x039e },
+ { "Yacute", 0x00dd },
+ { "Yuml", 0x0178 },
+ { "Zeta", 0x0396 },
+ { "aacute", 0x00e1 },
+ { "acirc", 0x00e2 },
+ { "acute", 0x00b4 },
+ { "aelig", 0x00e6 },
+ { "agrave", 0x00e0 },
+ { "alefsym", 0x2135 },
+ { "alpha", 0x03b1 },
+ { "amp", 38 },
+ { "and", 0x22a5 },
+ { "ang", 0x2220 },
+ { "apos", 0x0027 },
+ { "aring", 0x00e5 },
+ { "asymp", 0x2248 },
+ { "atilde", 0x00e3 },
+ { "auml", 0x00e4 },
+ { "bdquo", 0x201e },
+ { "beta", 0x03b2 },
+ { "brvbar", 0x00a6 },
+ { "bull", 0x2022 },
+ { "cap", 0x2229 },
+ { "ccedil", 0x00e7 },
+ { "cedil", 0x00b8 },
+ { "cent", 0x00a2 },
+ { "chi", 0x03c7 },
+ { "circ", 0x02c6 },
+ { "clubs", 0x2663 },
+ { "cong", 0x2245 },
+ { "copy", 0x00a9 },
+ { "crarr", 0x21b5 },
+ { "cup", 0x222a },
+ { "curren", 0x00a4 },
+ { "dArr", 0x21d3 },
+ { "dagger", 0x2020 },
+ { "darr", 0x2193 },
+ { "deg", 0x00b0 },
+ { "delta", 0x03b4 },
+ { "diams", 0x2666 },
+ { "divide", 0x00f7 },
+ { "eacute", 0x00e9 },
+ { "ecirc", 0x00ea },
+ { "egrave", 0x00e8 },
+ { "empty", 0x2205 },
+ { "emsp", 0x2003 },
+ { "ensp", 0x2002 },
+ { "epsilon", 0x03b5 },
+ { "equiv", 0x2261 },
+ { "eta", 0x03b7 },
+ { "eth", 0x00f0 },
+ { "euml", 0x00eb },
+ { "euro", 0x20ac },
+ { "exist", 0x2203 },
+ { "fnof", 0x0192 },
+ { "forall", 0x2200 },
+ { "frac12", 0x00bd },
+ { "frac14", 0x00bc },
+ { "frac34", 0x00be },
+ { "frasl", 0x2044 },
+ { "gamma", 0x03b3 },
+ { "ge", 0x2265 },
+ { "gt", 62 },
+ { "hArr", 0x21d4 },
+ { "harr", 0x2194 },
+ { "hearts", 0x2665 },
+ { "hellip", 0x2026 },
+ { "iacute", 0x00ed },
+ { "icirc", 0x00ee },
+ { "iexcl", 0x00a1 },
+ { "igrave", 0x00ec },
+ { "image", 0x2111 },
+ { "infin", 0x221e },
+ { "int", 0x222b },
+ { "iota", 0x03b9 },
+ { "iquest", 0x00bf },
+ { "isin", 0x2208 },
+ { "iuml", 0x00ef },
+ { "kappa", 0x03ba },
+ { "lArr", 0x21d0 },
+ { "lambda", 0x03bb },
+ { "lang", 0x2329 },
+ { "laquo", 0x00ab },
+ { "larr", 0x2190 },
+ { "lceil", 0x2308 },
+ { "ldquo", 0x201c },
+ { "le", 0x2264 },
+ { "lfloor", 0x230a },
+ { "lowast", 0x2217 },
+ { "loz", 0x25ca },
+ { "lrm", 0x200e },
+ { "lsaquo", 0x2039 },
+ { "lsquo", 0x2018 },
+ { "lt", 60 },
+ { "macr", 0x00af },
+ { "mdash", 0x2014 },
+ { "micro", 0x00b5 },
+ { "middot", 0x00b7 },
+ { "minus", 0x2212 },
+ { "mu", 0x03bc },
+ { "nabla", 0x2207 },
+ { "nbsp", 0x00a0 },
+ { "ndash", 0x2013 },
+ { "ne", 0x2260 },
+ { "ni", 0x220b },
+ { "not", 0x00ac },
+ { "notin", 0x2209 },
+ { "nsub", 0x2284 },
+ { "ntilde", 0x00f1 },
+ { "nu", 0x03bd },
+ { "oacute", 0x00f3 },
+ { "ocirc", 0x00f4 },
+ { "oelig", 0x0153 },
+ { "ograve", 0x00f2 },
+ { "oline", 0x203e },
+ { "omega", 0x03c9 },
+ { "omicron", 0x03bf },
+ { "oplus", 0x2295 },
+ { "or", 0x22a6 },
+ { "ordf", 0x00aa },
+ { "ordm", 0x00ba },
+ { "oslash", 0x00f8 },
+ { "otilde", 0x00f5 },
+ { "otimes", 0x2297 },
+ { "ouml", 0x00f6 },
+ { "para", 0x00b6 },
+ { "part", 0x2202 },
+ { "percnt", 0x0025 },
+ { "permil", 0x2030 },
+ { "perp", 0x22a5 },
+ { "phi", 0x03c6 },
+ { "pi", 0x03c0 },
+ { "piv", 0x03d6 },
+ { "plusmn", 0x00b1 },
+ { "pound", 0x00a3 },
+ { "prime", 0x2032 },
+ { "prod", 0x220f },
+ { "prop", 0x221d },
+ { "psi", 0x03c8 },
+ { "quot", 34 },
+ { "rArr", 0x21d2 },
+ { "radic", 0x221a },
+ { "rang", 0x232a },
+ { "raquo", 0x00bb },
+ { "rarr", 0x2192 },
+ { "rceil", 0x2309 },
+ { "rdquo", 0x201d },
+ { "real", 0x211c },
+ { "reg", 0x00ae },
+ { "rfloor", 0x230b },
+ { "rho", 0x03c1 },
+ { "rlm", 0x200f },
+ { "rsaquo", 0x203a },
+ { "rsquo", 0x2019 },
+ { "sbquo", 0x201a },
+ { "scaron", 0x0161 },
+ { "sdot", 0x22c5 },
+ { "sect", 0x00a7 },
+ { "shy", 0x00ad },
+ { "sigma", 0x03c3 },
+ { "sigmaf", 0x03c2 },
+ { "sim", 0x223c },
+ { "spades", 0x2660 },
+ { "sub", 0x2282 },
+ { "sube", 0x2286 },
+ { "sum", 0x2211 },
+ { "sup", 0x2283 },
+ { "sup1", 0x00b9 },
+ { "sup2", 0x00b2 },
+ { "sup3", 0x00b3 },
+ { "supe", 0x2287 },
+ { "szlig", 0x00df },
+ { "tau", 0x03c4 },
+ { "there4", 0x2234 },
+ { "theta", 0x03b8 },
+ { "thetasym", 0x03d1 },
+ { "thinsp", 0x2009 },
+ { "thorn", 0x00fe },
+ { "tilde", 0x02dc },
+ { "times", 0x00d7 },
+ { "trade", 0x2122 },
+ { "uArr", 0x21d1 },
+ { "uacute", 0x00fa },
+ { "uarr", 0x2191 },
+ { "ucirc", 0x00fb },
+ { "ugrave", 0x00f9 },
+ { "uml", 0x00a8 },
+ { "upsih", 0x03d2 },
+ { "upsilon", 0x03c5 },
+ { "uuml", 0x00fc },
+ { "weierp", 0x2118 },
+ { "xi", 0x03be },
+ { "yacute", 0x00fd },
+ { "yen", 0x00a5 },
+ { "yuml", 0x00ff },
+ { "zeta", 0x03b6 },
+ { "zwj", 0x200d },
+ { "zwnj", 0x200c }
+};
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &entityStr, const QTextHtmlEntity &entity)
+{
+ return entityStr < QLatin1String(entity.name);
+}
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlEntity &entity, const QString &entityStr)
+{
+ return QLatin1String(entity.name) < entityStr;
+}
+
+static QChar resolveEntity(const QString &entity)
+{
+ const QTextHtmlEntity *start = &entities[0];
+ const QTextHtmlEntity *end = &entities[MAX_ENTITY];
+ const QTextHtmlEntity *e = qBinaryFind(start, end, entity);
+ if (e == end)
+ return QChar();
+ return e->code;
+}
+
+static const ushort windowsLatin1ExtendedCharacters[0xA0 - 0x80] = {
+ 0x20ac, // 0x80
+ 0x0081, // 0x81 direct mapping
+ 0x201a, // 0x82
+ 0x0192, // 0x83
+ 0x201e, // 0x84
+ 0x2026, // 0x85
+ 0x2020, // 0x86
+ 0x2021, // 0x87
+ 0x02C6, // 0x88
+ 0x2030, // 0x89
+ 0x0160, // 0x8A
+ 0x2039, // 0x8B
+ 0x0152, // 0x8C
+ 0x008D, // 0x8D direct mapping
+ 0x017D, // 0x8E
+ 0x008F, // 0x8F directmapping
+ 0x0090, // 0x90 directmapping
+ 0x2018, // 0x91
+ 0x2019, // 0x92
+ 0x201C, // 0x93
+ 0X201D, // 0x94
+ 0x2022, // 0x95
+ 0x2013, // 0x96
+ 0x2014, // 0x97
+ 0x02DC, // 0x98
+ 0x2122, // 0x99
+ 0x0161, // 0x9A
+ 0x203A, // 0x9B
+ 0x0153, // 0x9C
+ 0x009D, // 0x9D direct mapping
+ 0x017E, // 0x9E
+ 0x0178 // 0x9F
+};
+
+// the displayMode value is according to the what are blocks in the piecetable, not
+// what the w3c defines.
+static const QTextHtmlElement elements[Html_NumElements]= {
+ { "a", Html_a, QTextHtmlElement::DisplayInline },
+ { "address", Html_address, QTextHtmlElement::DisplayInline },
+ { "b", Html_b, QTextHtmlElement::DisplayInline },
+ { "big", Html_big, QTextHtmlElement::DisplayInline },
+ { "blockquote", Html_blockquote, QTextHtmlElement::DisplayBlock },
+ { "body", Html_body, QTextHtmlElement::DisplayBlock },
+ { "br", Html_br, QTextHtmlElement::DisplayInline },
+ { "caption", Html_caption, QTextHtmlElement::DisplayBlock },
+ { "center", Html_center, QTextHtmlElement::DisplayBlock },
+ { "cite", Html_cite, QTextHtmlElement::DisplayInline },
+ { "code", Html_code, QTextHtmlElement::DisplayInline },
+ { "dd", Html_dd, QTextHtmlElement::DisplayBlock },
+ { "dfn", Html_dfn, QTextHtmlElement::DisplayInline },
+ { "div", Html_div, QTextHtmlElement::DisplayBlock },
+ { "dl", Html_dl, QTextHtmlElement::DisplayBlock },
+ { "dt", Html_dt, QTextHtmlElement::DisplayBlock },
+ { "em", Html_em, QTextHtmlElement::DisplayInline },
+ { "font", Html_font, QTextHtmlElement::DisplayInline },
+ { "h1", Html_h1, QTextHtmlElement::DisplayBlock },
+ { "h2", Html_h2, QTextHtmlElement::DisplayBlock },
+ { "h3", Html_h3, QTextHtmlElement::DisplayBlock },
+ { "h4", Html_h4, QTextHtmlElement::DisplayBlock },
+ { "h5", Html_h5, QTextHtmlElement::DisplayBlock },
+ { "h6", Html_h6, QTextHtmlElement::DisplayBlock },
+ { "head", Html_head, QTextHtmlElement::DisplayNone },
+ { "hr", Html_hr, QTextHtmlElement::DisplayBlock },
+ { "html", Html_html, QTextHtmlElement::DisplayInline },
+ { "i", Html_i, QTextHtmlElement::DisplayInline },
+ { "img", Html_img, QTextHtmlElement::DisplayInline },
+ { "kbd", Html_kbd, QTextHtmlElement::DisplayInline },
+ { "li", Html_li, QTextHtmlElement::DisplayBlock },
+ { "link", Html_link, QTextHtmlElement::DisplayNone },
+ { "meta", Html_meta, QTextHtmlElement::DisplayNone },
+ { "nobr", Html_nobr, QTextHtmlElement::DisplayInline },
+ { "ol", Html_ol, QTextHtmlElement::DisplayBlock },
+ { "p", Html_p, QTextHtmlElement::DisplayBlock },
+ { "pre", Html_pre, QTextHtmlElement::DisplayBlock },
+ { "qt", Html_body /*deliberate mapping*/, QTextHtmlElement::DisplayBlock },
+ { "s", Html_s, QTextHtmlElement::DisplayInline },
+ { "samp", Html_samp, QTextHtmlElement::DisplayInline },
+ { "script", Html_script, QTextHtmlElement::DisplayNone },
+ { "small", Html_small, QTextHtmlElement::DisplayInline },
+ { "span", Html_span, QTextHtmlElement::DisplayInline },
+ { "strong", Html_strong, QTextHtmlElement::DisplayInline },
+ { "style", Html_style, QTextHtmlElement::DisplayNone },
+ { "sub", Html_sub, QTextHtmlElement::DisplayInline },
+ { "sup", Html_sup, QTextHtmlElement::DisplayInline },
+ { "table", Html_table, QTextHtmlElement::DisplayTable },
+ { "tbody", Html_tbody, QTextHtmlElement::DisplayTable },
+ { "td", Html_td, QTextHtmlElement::DisplayBlock },
+ { "tfoot", Html_tfoot, QTextHtmlElement::DisplayTable },
+ { "th", Html_th, QTextHtmlElement::DisplayBlock },
+ { "thead", Html_thead, QTextHtmlElement::DisplayTable },
+ { "title", Html_title, QTextHtmlElement::DisplayNone },
+ { "tr", Html_tr, QTextHtmlElement::DisplayTable },
+ { "tt", Html_tt, QTextHtmlElement::DisplayInline },
+ { "u", Html_u, QTextHtmlElement::DisplayInline },
+ { "ul", Html_ul, QTextHtmlElement::DisplayBlock },
+ { "var", Html_var, QTextHtmlElement::DisplayInline },
+};
+
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &str, const QTextHtmlElement &e)
+{
+ return str < QLatin1String(e.name);
+}
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlElement &e, const QString &str)
+{
+ return QLatin1String(e.name) < str;
+}
+
+static const QTextHtmlElement *lookupElementHelper(const QString &element)
+{
+ const QTextHtmlElement *start = &elements[0];
+ const QTextHtmlElement *end = &elements[Html_NumElements];
+ const QTextHtmlElement *e = qBinaryFind(start, end, element);
+ if (e == end)
+ return 0;
+ return e;
+}
+
+int QTextHtmlParser::lookupElement(const QString &element)
+{
+ const QTextHtmlElement *e = lookupElementHelper(element);
+ if (!e)
+ return -1;
+ return e->id;
+}
+
+// quotes newlines as "\\n"
+static QString quoteNewline(const QString &s)
+{
+ QString n = s;
+ n.replace(QLatin1Char('\n'), QLatin1String("\\n"));
+ return n;
+}
+
+QTextHtmlParserNode::QTextHtmlParserNode()
+ : parent(0), id(Html_unknown),
+ cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false),
+ hasCssListIndent(false), isEmptyParagraph(false), isTextFrame(false), isRootFrame(false),
+ displayMode(QTextHtmlElement::DisplayInline), hasHref(false),
+ listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
+ tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0),
+ borderBrush(Qt::darkGray), borderStyle(QTextFrameFormat::BorderStyle_Outset),
+ userState(-1), cssListIndent(0), wsm(WhiteSpaceModeUndefined)
+{
+ margin[QTextHtmlParser::MarginLeft] = 0;
+ margin[QTextHtmlParser::MarginRight] = 0;
+ margin[QTextHtmlParser::MarginTop] = 0;
+ margin[QTextHtmlParser::MarginBottom] = 0;
+}
+
+void QTextHtmlParser::dumpHtml()
+{
+ for (int i = 0; i < count(); ++i) {
+ qDebug().nospace() << qPrintable(QString(depth(i)*4, QLatin1Char(' ')))
+ << qPrintable(at(i).tag) << ':'
+ << quoteNewline(at(i).text);
+ ;
+ }
+}
+
+QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
+{
+ QTextHtmlParserNode *lastNode = &nodes.last();
+ QTextHtmlParserNode *newNode = 0;
+
+ bool reuseLastNode = true;
+
+ if (nodes.count() == 1) {
+ reuseLastNode = false;
+ } else if (lastNode->tag.isEmpty()) {
+
+ if (lastNode->text.isEmpty()) {
+ reuseLastNode = true;
+ } else { // last node is a text node (empty tag) with some text
+
+ if (lastNode->text.length() == 1 && lastNode->text.at(0).isSpace()) {
+
+ int lastSibling = count() - 2;
+ while (lastSibling
+ && at(lastSibling).parent != lastNode->parent
+ && at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
+ lastSibling = at(lastSibling).parent;
+ }
+
+ if (at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
+ reuseLastNode = false;
+ } else {
+ reuseLastNode = true;
+ }
+ } else {
+ // text node with real (non-whitespace) text -> nothing to re-use
+ reuseLastNode = false;
+ }
+
+ }
+
+ } else {
+ // last node had a proper tag -> nothing to re-use
+ reuseLastNode = false;
+ }
+
+ if (reuseLastNode) {
+ newNode = lastNode;
+ newNode->tag.clear();
+ newNode->text.clear();
+ newNode->id = Html_unknown;
+ } else {
+ nodes.resize(nodes.size() + 1);
+ newNode = &nodes.last();
+ }
+
+ newNode->parent = parent;
+ return newNode;
+}
+
+void QTextHtmlParser::parse(const QString &text, const QTextDocument *_resourceProvider)
+{
+ nodes.clear();
+ nodes.resize(1);
+ txt = text;
+ pos = 0;
+ len = txt.length();
+ textEditMode = false;
+ resourceProvider = _resourceProvider;
+ parse();
+ //dumpHtml();
+}
+
+int QTextHtmlParser::depth(int i) const
+{
+ int depth = 0;
+ while (i) {
+ i = at(i).parent;
+ ++depth;
+ }
+ return depth;
+}
+
+int QTextHtmlParser::margin(int i, int mar) const {
+ int m = 0;
+ const QTextHtmlParserNode *node;
+ if (mar == MarginLeft
+ || mar == MarginRight) {
+ while (i) {
+ node = &at(i);
+ if (!node->isBlock() && node->id != Html_table)
+ break;
+ if (node->isTableCell())
+ break;
+ m += node->margin[mar];
+ i = node->parent;
+ }
+ }
+ return m;
+}
+
+int QTextHtmlParser::topMargin(int i) const
+{
+ if (!i)
+ return 0;
+ return at(i).margin[MarginTop];
+}
+
+int QTextHtmlParser::bottomMargin(int i) const
+{
+ if (!i)
+ return 0;
+ return at(i).margin[MarginBottom];
+}
+
+void QTextHtmlParser::eatSpace()
+{
+ while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
+ pos++;
+}
+
+void QTextHtmlParser::parse()
+{
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('<')) {
+ parseTag();
+ } else if (c == QLatin1Char('&')) {
+ nodes.last().text += parseEntity();
+ } else {
+ nodes.last().text += c;
+ }
+ }
+}
+
+// parses a tag after "<"
+void QTextHtmlParser::parseTag()
+{
+ eatSpace();
+
+ // handle comments and other exclamation mark declarations
+ if (hasPrefix(QLatin1Char('!'))) {
+ parseExclamationTag();
+ if (nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePre
+ && nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePreWrap
+ && !textEditMode)
+ eatSpace();
+ return;
+ }
+
+ // if close tag just close
+ if (hasPrefix(QLatin1Char('/'))) {
+ if (nodes.last().id == Html_style) {
+#ifndef QT_NO_CSSPARSER
+ QCss::Parser parser(nodes.last().text);
+ QCss::StyleSheet sheet;
+ sheet.origin = QCss::StyleSheetOrigin_Author;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ inlineStyleSheets.append(sheet);
+ resolveStyleSheetImports(sheet);
+#endif
+ }
+ parseCloseTag();
+ return;
+ }
+
+ int p = last();
+ while (p && at(p).tag.size() == 0)
+ p = at(p).parent;
+
+ QTextHtmlParserNode *node = newNode(p);
+
+ // parse tag name
+ node->tag = parseWord().toLower();
+
+ const QTextHtmlElement *elem = lookupElementHelper(node->tag);
+ if (elem) {
+ node->id = elem->id;
+ node->displayMode = elem->displayMode;
+ } else {
+ node->id = Html_unknown;
+ }
+
+ node->attributes.clear();
+ // _need_ at least one space after the tag name, otherwise there can't be attributes
+ if (pos < len && txt.at(pos).isSpace())
+ node->attributes = parseAttributes();
+
+ // resolveParent() may have to change the order in the tree and
+ // insert intermediate nodes for buggy HTML, so re-initialize the 'node'
+ // pointer through the return value
+ node = resolveParent();
+ resolveNode();
+
+ const int nodeIndex = nodes.count() - 1; // this new node is always the last
+#ifndef QT_NO_CSSPARSER
+ node->applyCssDeclarations(declarationsForNode(nodeIndex), resourceProvider);
+#endif
+ applyAttributes(node->attributes);
+
+ // finish tag
+ bool tagClosed = false;
+ while (pos < len && txt.at(pos) != QLatin1Char('>')) {
+ if (txt.at(pos) == QLatin1Char('/'))
+ tagClosed = true;
+
+ pos++;
+ }
+ pos++;
+
+ // in a white-space preserving environment strip off a initial newline
+ // since the element itself already generates a newline
+ if ((node->wsm == QTextHtmlParserNode::WhiteSpacePre
+ || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ && node->isBlock()) {
+ if (pos < len - 1 && txt.at(pos) == QLatin1Char('\n'))
+ ++pos;
+ }
+
+ if (node->mayNotHaveChildren() || tagClosed) {
+ newNode(node->parent);
+ resolveNode();
+ }
+}
+
+// parses a tag beginning with "/"
+void QTextHtmlParser::parseCloseTag()
+{
+ ++pos;
+ QString tag = parseWord().toLower().trimmed();
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>'))
+ break;
+ }
+
+ // find corresponding open node
+ int p = last();
+ if (p > 0
+ && at(p - 1).tag == tag
+ && at(p - 1).mayNotHaveChildren())
+ p--;
+
+ while (p && at(p).tag != tag)
+ p = at(p).parent;
+
+ // simply ignore the tag if we can't find
+ // a corresponding open node, for broken
+ // html such as <font>blah</font></font>
+ if (!p)
+ return;
+
+ // in a white-space preserving environment strip off a trailing newline
+ // since the closing of the opening block element will automatically result
+ // in a new block for elements following the <pre>
+ // ...foo\n</pre><p>blah -> foo</pre><p>blah
+ if ((at(p).wsm == QTextHtmlParserNode::WhiteSpacePre
+ || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ && at(p).isBlock()) {
+ if (at(last()).text.endsWith(QLatin1Char('\n')))
+ nodes[last()].text.chop(1);
+ }
+
+ newNode(at(p).parent);
+ resolveNode();
+}
+
+// parses a tag beginning with "!"
+void QTextHtmlParser::parseExclamationTag()
+{
+ ++pos;
+ if (hasPrefix(QLatin1Char('-'),1) && hasPrefix(QLatin1Char('-'),2)) {
+ pos += 3;
+ // eat comments
+ int end = txt.indexOf(QLatin1String("-->"), pos);
+ pos = (end >= 0 ? end + 3 : len);
+ } else {
+ // eat internal tags
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>'))
+ break;
+ }
+ }
+}
+
+// parses an entity after "&", and returns it
+QString QTextHtmlParser::parseEntity()
+{
+ int recover = pos;
+ QString entity;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c.isSpace() || pos - recover > 9) {
+ goto error;
+ }
+ if (c == QLatin1Char(';'))
+ break;
+ entity += c;
+ }
+ {
+ QChar resolved = resolveEntity(entity);
+ if (!resolved.isNull())
+ return QString(resolved);
+ }
+ if (entity.length() > 1 && entity.at(0) == QLatin1Char('#')) {
+ entity.remove(0, 1); // removing leading #
+
+ int base = 10;
+ bool ok = false;
+
+ if (entity.at(0).toLower() == QLatin1Char('x')) { // hex entity?
+ entity.remove(0, 1);
+ base = 16;
+ }
+
+ uint uc = entity.toUInt(&ok, base);
+ if (ok) {
+ if (uc >= 0x80 && uc < 0x80 + (sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0])))
+ uc = windowsLatin1ExtendedCharacters[uc - 0x80];
+ QString str;
+ if (uc > 0xffff) {
+ // surrogate pair
+ uc -= 0x10000;
+ ushort high = uc/0x400 + 0xd800;
+ ushort low = uc%0x400 + 0xdc00;
+ str.append(QChar(high));
+ str.append(QChar(low));
+ } else {
+ str.append(QChar(uc));
+ }
+ return str;
+ }
+ }
+error:
+ pos = recover;
+ return QLatin1String("&");
+}
+
+// parses one word, possibly quoted, and returns it
+QString QTextHtmlParser::parseWord()
+{
+ QString word;
+ if (hasPrefix(QLatin1Char('\"'))) { // double quotes
+ ++pos;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('\"'))
+ break;
+ else if (c == QLatin1Char('&'))
+ word += parseEntity();
+ else
+ word += c;
+ }
+ } else if (hasPrefix(QLatin1Char('\''))) { // single quotes
+ ++pos;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('\''))
+ break;
+ else
+ word += c;
+ }
+ } else { // normal text
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>')
+ || (c == QLatin1Char('/') && hasPrefix(QLatin1Char('>'), 1))
+ || c == QLatin1Char('<')
+ || c == QLatin1Char('=')
+ || c.isSpace()) {
+ --pos;
+ break;
+ }
+ if (c == QLatin1Char('&'))
+ word += parseEntity();
+ else
+ word += c;
+ }
+ }
+ return word;
+}
+
+// gives the new node the right parent
+QTextHtmlParserNode *QTextHtmlParser::resolveParent()
+{
+ QTextHtmlParserNode *node = &nodes.last();
+
+ int p = node->parent;
+
+ // Excel gives us buggy HTML with just tr without surrounding table tags
+ // or with just td tags
+
+ if (node->id == Html_td) {
+ int n = p;
+ while (n && at(n).id != Html_tr)
+ n = at(n).parent;
+
+ if (!n) {
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+
+ QTextHtmlParserNode *table = &nodes[nodes.count() - 3];
+ table->parent = p;
+ table->id = Html_table;
+ table->tag = QLatin1String("table");
+ table->children.append(nodes.count() - 2); // add row as child
+
+ QTextHtmlParserNode *row = &nodes[nodes.count() - 2];
+ row->parent = nodes.count() - 3; // table as parent
+ row->id = Html_tr;
+ row->tag = QLatin1String("tr");
+
+ p = nodes.count() - 2;
+ node = &nodes.last(); // re-initialize pointer
+ }
+ }
+
+ if (node->id == Html_tr) {
+ int n = p;
+ while (n && at(n).id != Html_table)
+ n = at(n).parent;
+
+ if (!n) {
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+ QTextHtmlParserNode *table = &nodes[nodes.count() - 2];
+ table->parent = p;
+ table->id = Html_table;
+ table->tag = QLatin1String("table");
+ p = nodes.count() - 2;
+ node = &nodes.last(); // re-initialize pointer
+ }
+ }
+
+ // permit invalid html by letting block elements be children
+ // of inline elements with the exception of paragraphs:
+ //
+ // a new paragraph closes parent inline elements (while loop),
+ // unless they themselves are children of a non-paragraph block
+ // element (if statement)
+ //
+ // For example:
+ //
+ // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
+ // belongs to the first <p>. The self-nesting
+ // check further down prevents the second <p>
+ // from nesting into the first one then.
+ // so Bar is not bold.
+ //
+ // <body><b><p>Foo <-- Foo should be bold.
+ //
+ // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
+ //
+ if (node->id == Html_p) {
+ while (p && !at(p).isBlock())
+ p = at(p).parent;
+
+ if (!p || at(p).id != Html_p)
+ p = node->parent;
+ }
+
+ // some elements are not self nesting
+ if (node->id == at(p).id
+ && node->isNotSelfNesting())
+ p = at(p).parent;
+
+ // some elements are not allowed in certain contexts
+ while ((p && !node->allowedInContext(at(p).id))
+ // ### make new styles aware of empty tags
+ || at(p).mayNotHaveChildren()
+ ) {
+ p = at(p).parent;
+ }
+
+ node->parent = p;
+
+ // makes it easier to traverse the tree, later
+ nodes[p].children.append(nodes.count() - 1);
+ return node;
+}
+
+// sets all properties on the new node
+void QTextHtmlParser::resolveNode()
+{
+ QTextHtmlParserNode *node = &nodes.last();
+ const QTextHtmlParserNode *parent = &nodes.at(node->parent);
+ node->initializeProperties(parent, this);
+}
+
+bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
+{
+ if (!isListStart())
+ return false;
+
+ int p = parent;
+ while (p) {
+ if (parser->at(p).isListStart())
+ return true;
+ p = parser->at(p).parent;
+ }
+ return false;
+}
+
+void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
+{
+ // inherit properties from parent element
+ charFormat = parent->charFormat;
+
+ if (id == Html_html)
+ blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
+ else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
+ blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
+
+ if (parent->displayMode == QTextHtmlElement::DisplayNone)
+ displayMode = QTextHtmlElement::DisplayNone;
+
+ if (parent->id != Html_table || id == Html_caption) {
+ if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
+ blockFormat.setAlignment(parent->blockFormat.alignment());
+ else
+ blockFormat.clearProperty(QTextFormat::BlockAlignment);
+ }
+ // we don't paint per-row background colors, yet. so as an
+ // exception inherit the background color here
+ // we also inherit the background between inline elements
+ if ((parent->id != Html_tr || !isTableCell())
+ && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)) {
+ charFormat.clearProperty(QTextFormat::BackgroundBrush);
+ }
+
+ listStyle = parent->listStyle;
+ // makes no sense to inherit that property, a named anchor is a single point
+ // in the document, which is set by the DocumentFragment
+ charFormat.clearProperty(QTextFormat::AnchorName);
+ wsm = parent->wsm;
+
+ // initialize remaining properties
+ margin[QTextHtmlParser::MarginLeft] = 0;
+ margin[QTextHtmlParser::MarginRight] = 0;
+ margin[QTextHtmlParser::MarginTop] = 0;
+ margin[QTextHtmlParser::MarginBottom] = 0;
+ cssFloat = QTextFrameFormat::InFlow;
+
+ for (int i = 0; i < 4; ++i)
+ padding[i] = -1;
+
+ // set element specific attributes
+ switch (id) {
+ case Html_a:
+ charFormat.setAnchor(true);
+ for (int i = 0; i < attributes.count(); i += 2) {
+ const QString key = attributes.at(i);
+ if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
+ && !attributes.at(i + 1).isEmpty()) {
+ hasHref = true;
+ charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ charFormat.setForeground(QApplication::palette().link());
+ }
+ }
+
+ break;
+ case Html_em:
+ case Html_i:
+ case Html_cite:
+ case Html_address:
+ case Html_var:
+ case Html_dfn:
+ charFormat.setFontItalic(true);
+ break;
+ case Html_big:
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
+ break;
+ case Html_small:
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
+ break;
+ case Html_strong:
+ case Html_b:
+ charFormat.setFontWeight(QFont::Bold);
+ break;
+ case Html_h1:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
+ margin[QTextHtmlParser::MarginTop] = 18;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h2:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
+ margin[QTextHtmlParser::MarginTop] = 16;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h3:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
+ margin[QTextHtmlParser::MarginTop] = 14;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h4:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h5:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 4;
+ break;
+ case Html_p:
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_center:
+ blockFormat.setAlignment(Qt::AlignCenter);
+ break;
+ case Html_ul:
+ listStyle = QTextListFormat::ListDisc;
+ // nested lists don't have margins, except for the toplevel one
+ if (!isNestedList(parser)) {
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ }
+ // no left margin as we use indenting instead
+ break;
+ case Html_ol:
+ listStyle = QTextListFormat::ListDecimal;
+ // nested lists don't have margins, except for the toplevel one
+ if (!isNestedList(parser)) {
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ }
+ // no left margin as we use indenting instead
+ break;
+ case Html_code:
+ case Html_tt:
+ case Html_kbd:
+ case Html_samp:
+ charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
+ // <tt> uses a fixed font, so set the property
+ charFormat.setFontFixedPitch(true);
+ break;
+ case Html_br:
+ text = QChar(QChar::LineSeparator);
+ wsm = QTextHtmlParserNode::WhiteSpacePre;
+ break;
+ // ##### sub / sup
+ case Html_pre:
+ charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
+ wsm = WhiteSpacePre;
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ // <pre> uses a fixed font
+ charFormat.setFontFixedPitch(true);
+ break;
+ case Html_blockquote:
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ margin[QTextHtmlParser::MarginLeft] = 40;
+ margin[QTextHtmlParser::MarginRight] = 40;
+ break;
+ case Html_dl:
+ margin[QTextHtmlParser::MarginTop] = 8;
+ margin[QTextHtmlParser::MarginBottom] = 8;
+ break;
+ case Html_dd:
+ margin[QTextHtmlParser::MarginLeft] = 30;
+ break;
+ case Html_u:
+ charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ break;
+ case Html_s:
+ charFormat.setFontStrikeOut(true);
+ break;
+ case Html_nobr:
+ wsm = WhiteSpaceNoWrap;
+ break;
+ case Html_th:
+ charFormat.setFontWeight(QFont::Bold);
+ blockFormat.setAlignment(Qt::AlignCenter);
+ break;
+ case Html_td:
+ blockFormat.setAlignment(Qt::AlignLeft);
+ break;
+ case Html_sub:
+ charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
+ break;
+ case Html_sup:
+ charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
+ break;
+ default: break;
+ }
+}
+
+#ifndef QT_NO_CSSPARSER
+void QTextHtmlParserNode::setListStyle(const QVector<QCss::Value> &cssValues)
+{
+ for (int i = 0; i < cssValues.count(); ++i) {
+ if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
+ switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
+ case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
+ case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
+ case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
+ case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
+ case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
+ case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
+ case QCss::Value_LowerRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerRoman; break;
+ case QCss::Value_UpperRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperRoman; break;
+ default: break;
+ }
+ }
+ }
+ // allow individual list items to override the style
+ if (id == Html_li && hasOwnListStyle)
+ blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
+}
+
+void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
+{
+ QCss::ValueExtractor extractor(declarations);
+ extractor.extractBox(margin, padding);
+
+ for (int i = 0; i < declarations.count(); ++i) {
+ const QCss::Declaration &decl = declarations.at(i);
+ if (decl.d->values.isEmpty()) continue;
+
+ QCss::KnownValue identifier = QCss::UnknownValue;
+ if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
+ identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
+
+ switch (decl.d->propertyId) {
+ case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
+ case QCss::BorderStyles:
+ if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
+ borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
+ break;
+ case QCss::BorderWidth:
+ tableBorder = extractor.lengthValue(decl);
+ break;
+ case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
+ case QCss::Float:
+ cssFloat = QTextFrameFormat::InFlow;
+ switch (identifier) {
+ case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
+ case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
+ default: break;
+ }
+ break;
+ case QCss::QtBlockIndent:
+ blockFormat.setIndent(decl.d->values.first().variant.toInt());
+ break;
+ case QCss::LineHeight: {
+ qreal lineHeight;
+ if (decl.realValue(&lineHeight, "px")) {
+ blockFormat.setLineHeight(lineHeight, QTextBlockFormat::FixedHeight);
+ } else {
+ bool ok;
+ QString value = decl.d->values.first().toString();
+ lineHeight = value.toDouble(&ok);
+ if (ok)
+ blockFormat.setLineHeight(lineHeight, QTextBlockFormat::ProportionalHeight);
+ else
+ blockFormat.setLineHeight(0, QTextBlockFormat::SingleHeight);
+ }
+ break; }
+ case QCss::TextIndent: {
+ qreal indent = 0;
+ if (decl.realValue(&indent, "px"))
+ blockFormat.setTextIndent(indent);
+ break; }
+ case QCss::QtListIndent:
+ if (decl.intValue(&cssListIndent))
+ hasCssListIndent = true;
+ break;
+ case QCss::QtParagraphType:
+ if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
+ isEmptyParagraph = true;
+ break;
+ case QCss::QtTableType:
+ if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
+ isTextFrame = true;
+ else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
+ isTextFrame = true;
+ isRootFrame = true;
+ }
+ break;
+ case QCss::QtUserState:
+ userState = decl.d->values.first().variant.toInt();
+ break;
+ case QCss::Whitespace:
+ switch (identifier) {
+ case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
+ case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
+ case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
+ case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
+ default: break;
+ }
+ break;
+ case QCss::VerticalAlignment:
+ switch (identifier) {
+ case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
+ case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
+ case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
+ case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
+ case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
+ default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
+ }
+ break;
+ case QCss::PageBreakBefore:
+ switch (identifier) {
+ case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
+ case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
+ default: break;
+ }
+ break;
+ case QCss::PageBreakAfter:
+ switch (identifier) {
+ case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
+ case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
+ default: break;
+ }
+ break;
+ case QCss::TextUnderlineStyle:
+ switch (identifier) {
+ case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
+ case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
+ case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
+ case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
+ case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
+ case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
+ case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
+ default: break;
+ }
+ break;
+ case QCss::ListStyleType:
+ case QCss::ListStyle:
+ setListStyle(decl.d->values);
+ break;
+ case QCss::QtListNumberPrefix:
+ textListNumberPrefix = decl.d->values.first().variant.toString();
+ break;
+ case QCss::QtListNumberSuffix:
+ textListNumberSuffix = decl.d->values.first().variant.toString();
+ break;
+ default: break;
+ }
+ }
+
+ QFont f;
+ int adjustment = -255;
+ extractor.extractFont(&f, &adjustment);
+ if (f.resolve() & QFont::SizeResolved) {
+ if (f.pointSize() > 0) {
+ charFormat.setFontPointSize(f.pointSize());
+ } else if (f.pixelSize() > 0) {
+ charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
+ }
+ }
+ if (f.resolve() & QFont::StyleResolved)
+ charFormat.setFontItalic(f.style() != QFont::StyleNormal);
+
+ if (f.resolve() & QFont::WeightResolved)
+ charFormat.setFontWeight(f.weight());
+
+ if (f.resolve() & QFont::FamilyResolved)
+ charFormat.setFontFamily(f.family());
+
+ if (f.resolve() & QFont::UnderlineResolved)
+ charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
+
+ if (f.resolve() & QFont::OverlineResolved)
+ charFormat.setFontOverline(f.overline());
+
+ if (f.resolve() & QFont::StrikeOutResolved)
+ charFormat.setFontStrikeOut(f.strikeOut());
+
+ if (f.resolve() & QFont::CapitalizationResolved)
+ charFormat.setFontCapitalization(f.capitalization());
+
+ if (adjustment >= -1)
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
+
+ {
+ Qt::Alignment ignoredAlignment;
+ QCss::Repeat ignoredRepeat;
+ QString bgImage;
+ QBrush bgBrush;
+ QCss::Origin ignoredOrigin, ignoredClip;
+ QCss::Attachment ignoredAttachment;
+ extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
+ &ignoredOrigin, &ignoredAttachment, &ignoredClip);
+
+ if (!bgImage.isEmpty() && resourceProvider) {
+ applyBackgroundImage(bgImage, resourceProvider);
+ } else if (bgBrush.style() != Qt::NoBrush) {
+ charFormat.setBackground(bgBrush);
+ }
+ }
+}
+
+#endif // QT_NO_CSSPARSER
+
+void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
+{
+ if (!url.isEmpty() && resourceProvider) {
+ QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
+
+ if (qApp->thread() != QThread::currentThread()) {
+ // must use images in non-GUI threads
+ if (val.type() == QVariant::Image) {
+ QImage image = qvariant_cast<QImage>(val);
+ charFormat.setBackground(image);
+ } else if (val.type() == QVariant::ByteArray) {
+ QImage image;
+ if (image.loadFromData(val.toByteArray())) {
+ charFormat.setBackground(image);
+ }
+ }
+ } else {
+ if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
+ charFormat.setBackground(qvariant_cast<QPixmap>(val));
+ } else if (val.type() == QVariant::ByteArray) {
+ QPixmap pm;
+ if (pm.loadFromData(val.toByteArray())) {
+ charFormat.setBackground(pm);
+ }
+ }
+ }
+ }
+ if (!url.isEmpty())
+ charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
+}
+
+bool QTextHtmlParserNode::hasOnlyWhitespace() const
+{
+ for (int i = 0; i < text.count(); ++i)
+ if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
+ return false;
+ return true;
+}
+
+static bool setIntAttribute(int *destination, const QString &value)
+{
+ bool ok = false;
+ int val = value.toInt(&ok);
+ if (ok)
+ *destination = val;
+
+ return ok;
+}
+
+static bool setFloatAttribute(qreal *destination, const QString &value)
+{
+ bool ok = false;
+ qreal val = value.toDouble(&ok);
+ if (ok)
+ *destination = val;
+
+ return ok;
+}
+
+static void setWidthAttribute(QTextLength *width, QString value)
+{
+ bool ok = false;
+ qreal realVal = value.toDouble(&ok);
+ if (ok) {
+ *width = QTextLength(QTextLength::FixedLength, realVal);
+ } else {
+ value = value.trimmed();
+ if (!value.isEmpty() && value.endsWith(QLatin1Char('%'))) {
+ value.chop(1);
+ realVal = value.toDouble(&ok);
+ if (ok)
+ *width = QTextLength(QTextLength::PercentageLength, realVal);
+ }
+ }
+}
+
+#ifndef QT_NO_CSSPARSER
+void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
+{
+ QString css = value;
+ css.prepend(QLatin1String("* {"));
+ css.append(QLatin1Char('}'));
+ QCss::Parser parser(css);
+ QCss::StyleSheet sheet;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ if (sheet.styleRules.count() != 1) return;
+ applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
+}
+#endif
+
+QStringList QTextHtmlParser::parseAttributes()
+{
+ QStringList attrs;
+
+ while (pos < len) {
+ eatSpace();
+ if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
+ break;
+ QString key = parseWord().toLower();
+ QString value = QLatin1String("1");
+ if (key.size() == 0)
+ break;
+ eatSpace();
+ if (hasPrefix(QLatin1Char('='))){
+ pos++;
+ eatSpace();
+ value = parseWord();
+ }
+ if (value.size() == 0)
+ continue;
+ attrs << key << value;
+ }
+
+ return attrs;
+}
+
+void QTextHtmlParser::applyAttributes(const QStringList &attributes)
+{
+ // local state variable for qt3 textedit mode
+ bool seenQt3Richtext = false;
+ QString linkHref;
+ QString linkType;
+
+ if (attributes.count() % 2 == 1)
+ return;
+
+ QTextHtmlParserNode *node = &nodes.last();
+
+ for (int i = 0; i < attributes.count(); i += 2) {
+ QString key = attributes.at(i);
+ QString value = attributes.at(i + 1);
+
+ switch (node->id) {
+ case Html_font:
+ // the infamous font tag
+ if (key == QLatin1String("size") && value.size()) {
+ int n = value.toInt();
+ if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
+ n -= 3;
+ node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
+ } else if (key == QLatin1String("face")) {
+ node->charFormat.setFontFamily(value);
+ } else if (key == QLatin1String("color")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setForeground(c);
+ }
+ break;
+ case Html_ol:
+ case Html_ul:
+ if (key == QLatin1String("type")) {
+ node->hasOwnListStyle = true;
+ if (value == QLatin1String("1")) {
+ node->listStyle = QTextListFormat::ListDecimal;
+ } else if (value == QLatin1String("a")) {
+ node->listStyle = QTextListFormat::ListLowerAlpha;
+ } else if (value == QLatin1String("A")) {
+ node->listStyle = QTextListFormat::ListUpperAlpha;
+ } else if (value == QLatin1String("i")) {
+ node->listStyle = QTextListFormat::ListLowerRoman;
+ } else if (value == QLatin1String("I")) {
+ node->listStyle = QTextListFormat::ListUpperRoman;
+ } else {
+ value = value.toLower();
+ if (value == QLatin1String("square"))
+ node->listStyle = QTextListFormat::ListSquare;
+ else if (value == QLatin1String("disc"))
+ node->listStyle = QTextListFormat::ListDisc;
+ else if (value == QLatin1String("circle"))
+ node->listStyle = QTextListFormat::ListCircle;
+ }
+ }
+ break;
+ case Html_a:
+ if (key == QLatin1String("href"))
+ node->charFormat.setAnchorHref(value);
+ else if (key == QLatin1String("name"))
+ node->charFormat.setAnchorName(value);
+ break;
+ case Html_img:
+ if (key == QLatin1String("src") || key == QLatin1String("source")) {
+ node->imageName = value;
+ } else if (key == QLatin1String("width")) {
+ node->imageWidth = -2; // register that there is a value for it.
+ setFloatAttribute(&node->imageWidth, value);
+ } else if (key == QLatin1String("height")) {
+ node->imageHeight = -2; // register that there is a value for it.
+ setFloatAttribute(&node->imageHeight, value);
+ }
+ break;
+ case Html_tr:
+ case Html_body:
+ if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ }
+ break;
+ case Html_th:
+ case Html_td:
+ if (key == QLatin1String("width")) {
+ setWidthAttribute(&node->width, value);
+ } else if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ } else if (key == QLatin1String("rowspan")) {
+ if (setIntAttribute(&node->tableCellRowSpan, value))
+ node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
+ } else if (key == QLatin1String("colspan")) {
+ if (setIntAttribute(&node->tableCellColSpan, value))
+ node->tableCellColSpan = qMax(1, node->tableCellColSpan);
+ }
+ break;
+ case Html_table:
+ if (key == QLatin1String("border")) {
+ setFloatAttribute(&node->tableBorder, value);
+ } else if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ } else if (key == QLatin1String("cellspacing")) {
+ setFloatAttribute(&node->tableCellSpacing, value);
+ } else if (key == QLatin1String("cellpadding")) {
+ setFloatAttribute(&node->tableCellPadding, value);
+ } else if (key == QLatin1String("width")) {
+ setWidthAttribute(&node->width, value);
+ } else if (key == QLatin1String("height")) {
+ setWidthAttribute(&node->height, value);
+ }
+ break;
+ case Html_meta:
+ if (key == QLatin1String("name")
+ && value == QLatin1String("qrichtext")) {
+ seenQt3Richtext = true;
+ }
+
+ if (key == QLatin1String("content")
+ && value == QLatin1String("1")
+ && seenQt3Richtext) {
+
+ textEditMode = true;
+ }
+ break;
+ case Html_hr:
+ if (key == QLatin1String("width"))
+ setWidthAttribute(&node->width, value);
+ break;
+ case Html_link:
+ if (key == QLatin1String("href"))
+ linkHref = value;
+ else if (key == QLatin1String("type"))
+ linkType = value;
+ break;
+ default:
+ break;
+ }
+
+ if (key == QLatin1String("style")) {
+#ifndef QT_NO_CSSPARSER
+ node->parseStyleAttribute(value, resourceProvider);
+#endif
+ } else if (key == QLatin1String("align")) {
+ value = value.toLower();
+ bool alignmentSet = true;
+
+ if (value == QLatin1String("left"))
+ node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
+ else if (value == QLatin1String("right"))
+ node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
+ else if (value == QLatin1String("center"))
+ node->blockFormat.setAlignment(Qt::AlignHCenter);
+ else if (value == QLatin1String("justify"))
+ node->blockFormat.setAlignment(Qt::AlignJustify);
+ else
+ alignmentSet = false;
+
+ if (node->id == Html_img) {
+ // HTML4 compat
+ if (alignmentSet) {
+ if (node->blockFormat.alignment() & Qt::AlignLeft)
+ node->cssFloat = QTextFrameFormat::FloatLeft;
+ else if (node->blockFormat.alignment() & Qt::AlignRight)
+ node->cssFloat = QTextFrameFormat::FloatRight;
+ } else if (value == QLatin1String("middle")) {
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
+ } else if (value == QLatin1String("top")) {
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
+ }
+ }
+ } else if (key == QLatin1String("valign")) {
+ value = value.toLower();
+ if (value == QLatin1String("top"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
+ else if (value == QLatin1String("middle"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
+ else if (value == QLatin1String("bottom"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
+ } else if (key == QLatin1String("dir")) {
+ value = value.toLower();
+ if (value == QLatin1String("ltr"))
+ node->blockFormat.setLayoutDirection(Qt::LeftToRight);
+ else if (value == QLatin1String("rtl"))
+ node->blockFormat.setLayoutDirection(Qt::RightToLeft);
+ } else if (key == QLatin1String("title")) {
+ node->charFormat.setToolTip(value);
+ } else if (key == QLatin1String("id")) {
+ node->charFormat.setAnchor(true);
+ node->charFormat.setAnchorName(value);
+ }
+ }
+
+#ifndef QT_NO_CSSPARSER
+ if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
+ importStyleSheet(linkHref);
+#endif
+}
+
+#ifndef QT_NO_CSSPARSER
+class QTextHtmlStyleSelector : public QCss::StyleSelector
+{
+public:
+ inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
+ : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
+
+ virtual QStringList nodeNames(NodePtr node) const;
+ virtual QString attribute(NodePtr node, const QString &name) const;
+ virtual bool hasAttributes(NodePtr node) const;
+ virtual bool isNullNode(NodePtr node) const;
+ virtual NodePtr parentNode(NodePtr node) const;
+ virtual NodePtr previousSiblingNode(NodePtr node) const;
+ virtual NodePtr duplicateNode(NodePtr node) const;
+ virtual void freeNode(NodePtr node) const;
+
+private:
+ const QTextHtmlParser *parser;
+};
+
+QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
+{
+ return QStringList(parser->at(node.id).tag.toLower());
+}
+
+#endif // QT_NO_CSSPARSER
+
+static inline int findAttribute(const QStringList &attributes, const QString &name)
+{
+ int idx = -1;
+ do {
+ idx = attributes.indexOf(name, idx + 1);
+ } while (idx != -1 && (idx % 2 == 1));
+ return idx;
+}
+
+#ifndef QT_NO_CSSPARSER
+
+QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
+{
+ const QStringList &attributes = parser->at(node.id).attributes;
+ const int idx = findAttribute(attributes, name);
+ if (idx == -1)
+ return QString();
+ return attributes.at(idx + 1);
+}
+
+bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
+{
+ const QStringList &attributes = parser->at(node.id).attributes;
+ return !attributes.isEmpty();
+}
+
+bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
+{
+ return node.id == 0;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
+{
+ NodePtr parent;
+ parent.id = 0;
+ if (node.id) {
+ parent.id = parser->at(node.id).parent;
+ }
+ return parent;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
+{
+ return node;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
+{
+ NodePtr sibling;
+ sibling.id = 0;
+ if (!node.id)
+ return sibling;
+ int parent = parser->at(node.id).parent;
+ if (!parent)
+ return sibling;
+ const int childIdx = parser->at(parent).children.indexOf(node.id);
+ if (childIdx <= 0)
+ return sibling;
+ sibling.id = parser->at(parent).children.at(childIdx - 1);
+ return sibling;
+}
+
+void QTextHtmlStyleSelector::freeNode(NodePtr) const
+{
+}
+
+void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
+{
+ for (int i = 0; i < sheet.importRules.count(); ++i) {
+ const QCss::ImportRule &rule = sheet.importRules.at(i);
+ if (rule.media.isEmpty()
+ || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
+ importStyleSheet(rule.href);
+ }
+}
+
+void QTextHtmlParser::importStyleSheet(const QString &href)
+{
+ if (!resourceProvider)
+ return;
+ for (int i = 0; i < externalStyleSheets.count(); ++i)
+ if (externalStyleSheets.at(i).url == href)
+ return;
+
+ QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
+ QString css;
+ if (res.type() == QVariant::String) {
+ css = res.toString();
+ } else if (res.type() == QVariant::ByteArray) {
+ // #### detect @charset
+ css = QString::fromUtf8(res.toByteArray());
+ }
+ if (!css.isEmpty()) {
+ QCss::Parser parser(css);
+ QCss::StyleSheet sheet;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ externalStyleSheets.append(ExternalStyleSheet(href, sheet));
+ resolveStyleSheetImports(sheet);
+ }
+}
+
+QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
+{
+ QVector<QCss::Declaration> decls;
+
+ QTextHtmlStyleSelector selector(this);
+
+ int idx = 0;
+ selector.styleSheets.resize((resourceProvider ? 1 : 0)
+ + externalStyleSheets.count()
+ + inlineStyleSheets.count());
+ if (resourceProvider)
+ selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
+
+ for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
+ selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
+
+ for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
+ selector.styleSheets[idx] = inlineStyleSheets.at(i);
+
+ selector.medium = QLatin1String("screen");
+
+ QCss::StyleSelector::NodePtr n;
+ n.id = node;
+
+ const char *extraPseudo = 0;
+ if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
+ extraPseudo = "link";
+ decls = selector.declarationsForNode(n, extraPseudo);
+
+ return decls;
+}
+
+bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
+{
+ while (i) {
+ if (at(i).id == id)
+ return true;
+ i = at(i).parent;
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
+#endif // QT_NO_CSSPARSER
+
+#endif // QT_NO_TEXTHTMLPARSER