1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtexthtmlparser_p.h"
44 #include <qbytearray.h>
45 #include <qtextcodec.h>
46 #include <qapplication.h>
51 #include "qtextdocument.h"
52 #include "qtextformat_p.h"
53 #include "qtextdocument_p.h"
54 #include "qtextcursor.h"
56 #include "private/qfunctions_p.h"
58 #ifndef QT_NO_TEXTHTMLPARSER
62 // see also tst_qtextdocumentfragment.cpp
63 #define MAX_ENTITY 258
64 static const struct QTextHtmlEntity { const char *name; quint16 code; } entities[MAX_ENTITY]= {
83 { "Epsilon", 0x0395 },
100 { "Oacute", 0x00d3 },
102 { "Ograve", 0x00d2 },
104 { "Omicron", 0x039f },
105 { "Oslash", 0x00d8 },
106 { "Otilde", 0x00d5 },
114 { "Scaron", 0x0160 },
119 { "Uacute", 0x00da },
121 { "Ugrave", 0x00d9 },
122 { "Upsilon", 0x03a5 },
125 { "Yacute", 0x00dd },
128 { "aacute", 0x00e1 },
132 { "agrave", 0x00e0 },
133 { "alefsym", 0x2135 },
141 { "atilde", 0x00e3 },
145 { "brvbar", 0x00a6 },
148 { "ccedil", 0x00e7 },
158 { "curren", 0x00a4 },
160 { "dagger", 0x2020 },
165 { "divide", 0x00f7 },
166 { "eacute", 0x00e9 },
168 { "egrave", 0x00e8 },
172 { "epsilon", 0x03b5 },
180 { "forall", 0x2200 },
181 { "frac12", 0x00bd },
182 { "frac14", 0x00bc },
183 { "frac34", 0x00be },
190 { "hearts", 0x2665 },
191 { "hellip", 0x2026 },
192 { "iacute", 0x00ed },
195 { "igrave", 0x00ec },
200 { "iquest", 0x00bf },
205 { "lambda", 0x03bb },
212 { "lfloor", 0x230a },
213 { "lowast", 0x2217 },
216 { "lsaquo", 0x2039 },
222 { "middot", 0x00b7 },
233 { "ntilde", 0x00f1 },
235 { "oacute", 0x00f3 },
238 { "ograve", 0x00f2 },
241 { "omicron", 0x03bf },
246 { "oslash", 0x00f8 },
247 { "otilde", 0x00f5 },
248 { "otimes", 0x2297 },
252 { "percnt", 0x0025 },
253 { "permil", 0x2030 },
258 { "plusmn", 0x00b1 },
274 { "rfloor", 0x230b },
277 { "rsaquo", 0x203a },
280 { "scaron", 0x0161 },
285 { "sigmaf", 0x03c2 },
287 { "spades", 0x2660 },
298 { "there4", 0x2234 },
300 { "thetasym", 0x03d1 },
301 { "thinsp", 0x2009 },
307 { "uacute", 0x00fa },
310 { "ugrave", 0x00f9 },
313 { "upsilon", 0x03c5 },
315 { "weierp", 0x2118 },
317 { "yacute", 0x00fd },
325 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &entityStr, const QTextHtmlEntity &entity)
327 return entityStr < QLatin1String(entity.name);
330 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlEntity &entity, const QString &entityStr)
332 return QLatin1String(entity.name) < entityStr;
335 static QChar resolveEntity(const QString &entity)
337 const QTextHtmlEntity *start = &entities[0];
338 const QTextHtmlEntity *end = &entities[MAX_ENTITY];
339 const QTextHtmlEntity *e = qBinaryFind(start, end, entity);
345 static const ushort windowsLatin1ExtendedCharacters[0xA0 - 0x80] = {
347 0x0081, // 0x81 direct mapping
359 0x008D, // 0x8D direct mapping
361 0x008F, // 0x8F directmapping
362 0x0090, // 0x90 directmapping
375 0x009D, // 0x9D direct mapping
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 },
445 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &str, const QTextHtmlElement &e)
447 return str < QLatin1String(e.name);
450 Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlElement &e, const QString &str)
452 return QLatin1String(e.name) < str;
455 static const QTextHtmlElement *lookupElementHelper(const QString &element)
457 const QTextHtmlElement *start = &elements[0];
458 const QTextHtmlElement *end = &elements[Html_NumElements];
459 const QTextHtmlElement *e = qBinaryFind(start, end, element);
465 int QTextHtmlParser::lookupElement(const QString &element)
467 const QTextHtmlElement *e = lookupElementHelper(element);
473 // quotes newlines as "\\n"
474 static QString quoteNewline(const QString &s)
477 n.replace(QLatin1Char('\n'), QLatin1String("\\n"));
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)
491 margin[QTextHtmlParser::MarginLeft] = 0;
492 margin[QTextHtmlParser::MarginRight] = 0;
493 margin[QTextHtmlParser::MarginTop] = 0;
494 margin[QTextHtmlParser::MarginBottom] = 0;
497 void QTextHtmlParser::dumpHtml()
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);
507 QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
509 QTextHtmlParserNode *lastNode = &nodes.last();
510 QTextHtmlParserNode *newNode = 0;
512 bool reuseLastNode = true;
514 if (nodes.count() == 1) {
515 reuseLastNode = false;
516 } else if (lastNode->tag.isEmpty()) {
518 if (lastNode->text.isEmpty()) {
519 reuseLastNode = true;
520 } else { // last node is a text node (empty tag) with some text
522 if (lastNode->text.length() == 1 && lastNode->text.at(0).isSpace()) {
524 int lastSibling = count() - 2;
526 && at(lastSibling).parent != lastNode->parent
527 && at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
528 lastSibling = at(lastSibling).parent;
531 if (at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
532 reuseLastNode = false;
534 reuseLastNode = true;
537 // text node with real (non-whitespace) text -> nothing to re-use
538 reuseLastNode = false;
544 // last node had a proper tag -> nothing to re-use
545 reuseLastNode = false;
550 newNode->tag.clear();
551 newNode->text.clear();
552 newNode->id = Html_unknown;
554 nodes.resize(nodes.size() + 1);
555 newNode = &nodes.last();
558 newNode->parent = parent;
562 void QTextHtmlParser::parse(const QString &text, const QTextDocument *_resourceProvider)
569 textEditMode = false;
570 resourceProvider = _resourceProvider;
575 int QTextHtmlParser::depth(int i) const
585 int QTextHtmlParser::margin(int i, int mar) const {
587 const QTextHtmlParserNode *node;
588 if (mar == MarginLeft
589 || mar == MarginRight) {
592 if (!node->isBlock() && node->id != Html_table)
594 if (node->isTableCell())
596 m += node->margin[mar];
603 int QTextHtmlParser::topMargin(int i) const
607 return at(i).margin[MarginTop];
610 int QTextHtmlParser::bottomMargin(int i) const
614 return at(i).margin[MarginBottom];
617 void QTextHtmlParser::eatSpace()
619 while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
623 void QTextHtmlParser::parse()
626 QChar c = txt.at(pos++);
627 if (c == QLatin1Char('<')) {
629 } else if (c == QLatin1Char('&')) {
630 nodes.last().text += parseEntity();
632 nodes.last().text += c;
637 // parses a tag after "<"
638 void QTextHtmlParser::parseTag()
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
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);
669 while (p && at(p).tag.size() == 0)
672 QTextHtmlParserNode *node = newNode(p);
675 node->tag = parseWord().toLower();
677 const QTextHtmlElement *elem = lookupElementHelper(node->tag);
680 node->displayMode = elem->displayMode;
682 node->id = Html_unknown;
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();
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();
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);
700 applyAttributes(node->attributes);
703 bool tagClosed = false;
704 while (pos < len && txt.at(pos) != QLatin1Char('>')) {
705 if (txt.at(pos) == QLatin1Char('/'))
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'))
721 if (node->mayNotHaveChildren() || tagClosed) {
722 newNode(node->parent);
727 // parses a tag beginning with "/"
728 void QTextHtmlParser::parseCloseTag()
731 QString tag = parseWord().toLower().trimmed();
733 QChar c = txt.at(pos++);
734 if (c == QLatin1Char('>'))
738 // find corresponding open node
741 && at(p - 1).tag == tag
742 && at(p - 1).mayNotHaveChildren())
745 while (p && at(p).tag != tag)
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>
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);
765 newNode(at(p).parent);
769 // parses a tag beginning with "!"
770 void QTextHtmlParser::parseExclamationTag()
773 if (hasPrefix(QLatin1Char('-'),1) && hasPrefix(QLatin1Char('-'),2)) {
776 int end = txt.indexOf(QLatin1String("-->"), pos);
777 pos = (end >= 0 ? end + 3 : len);
781 QChar c = txt.at(pos++);
782 if (c == QLatin1Char('>'))
788 // parses an entity after "&", and returns it
789 QString QTextHtmlParser::parseEntity()
794 QChar c = txt.at(pos++);
795 if (c.isSpace() || pos - recover > 9) {
798 if (c == QLatin1Char(';'))
803 QChar resolved = resolveEntity(entity);
804 if (!resolved.isNull())
805 return QString(resolved);
807 if (entity.length() > 1 && entity.at(0) == QLatin1Char('#')) {
808 entity.remove(0, 1); // removing leading #
813 if (entity.at(0).toLower() == QLatin1Char('x')) { // hex entity?
818 uint uc = entity.toUInt(&ok, base);
820 if (uc >= 0x80 && uc < 0x80 + (sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0])))
821 uc = windowsLatin1ExtendedCharacters[uc - 0x80];
826 ushort high = uc/0x400 + 0xd800;
827 ushort low = uc%0x400 + 0xdc00;
828 str.append(QChar(high));
829 str.append(QChar(low));
831 str.append(QChar(uc));
838 return QLatin1String("&");
841 // parses one word, possibly quoted, and returns it
842 QString QTextHtmlParser::parseWord()
845 if (hasPrefix(QLatin1Char('\"'))) { // double quotes
848 QChar c = txt.at(pos++);
849 if (c == QLatin1Char('\"'))
851 else if (c == QLatin1Char('&'))
852 word += parseEntity();
856 } else if (hasPrefix(QLatin1Char('\''))) { // single quotes
859 QChar c = txt.at(pos++);
860 if (c == QLatin1Char('\''))
865 } else { // normal text
867 QChar c = txt.at(pos++);
868 if (c == QLatin1Char('>')
869 || (c == QLatin1Char('/') && hasPrefix(QLatin1Char('>'), 1))
870 || c == QLatin1Char('<')
871 || c == QLatin1Char('=')
876 if (c == QLatin1Char('&'))
877 word += parseEntity();
885 // gives the new node the right parent
886 QTextHtmlParserNode *QTextHtmlParser::resolveParent()
888 QTextHtmlParserNode *node = &nodes.last();
890 int p = node->parent;
892 // Excel gives us buggy HTML with just tr without surrounding table tags
893 // or with just td tags
895 if (node->id == Html_td) {
897 while (n && at(n).id != Html_tr)
901 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
902 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
904 QTextHtmlParserNode *table = &nodes[nodes.count() - 3];
906 table->id = Html_table;
907 table->tag = QLatin1String("table");
908 table->children.append(nodes.count() - 2); // add row as child
910 QTextHtmlParserNode *row = &nodes[nodes.count() - 2];
911 row->parent = nodes.count() - 3; // table as parent
913 row->tag = QLatin1String("tr");
915 p = nodes.count() - 2;
916 node = &nodes.last(); // re-initialize pointer
920 if (node->id == Html_tr) {
922 while (n && at(n).id != Html_table)
926 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
927 QTextHtmlParserNode *table = &nodes[nodes.count() - 2];
929 table->id = Html_table;
930 table->tag = QLatin1String("table");
931 p = nodes.count() - 2;
932 node = &nodes.last(); // re-initialize pointer
936 // permit invalid html by letting block elements be children
937 // of inline elements with the exception of paragraphs:
939 // a new paragraph closes parent inline elements (while loop),
940 // unless they themselves are children of a non-paragraph block
941 // element (if statement)
945 // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
946 // belongs to the first <p>. The self-nesting
947 // check further down prevents the second <p>
948 // from nesting into the first one then.
949 // so Bar is not bold.
951 // <body><b><p>Foo <-- Foo should be bold.
953 // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
955 if (node->id == Html_p) {
956 while (p && !at(p).isBlock())
959 if (!p || at(p).id != Html_p)
963 // some elements are not self nesting
964 if (node->id == at(p).id
965 && node->isNotSelfNesting())
968 // some elements are not allowed in certain contexts
969 while ((p && !node->allowedInContext(at(p).id))
970 // ### make new styles aware of empty tags
971 || at(p).mayNotHaveChildren()
978 // makes it easier to traverse the tree, later
979 nodes[p].children.append(nodes.count() - 1);
983 // sets all properties on the new node
984 void QTextHtmlParser::resolveNode()
986 QTextHtmlParserNode *node = &nodes.last();
987 const QTextHtmlParserNode *parent = &nodes.at(node->parent);
988 node->initializeProperties(parent, this);
991 bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
998 if (parser->at(p).isListStart())
1000 p = parser->at(p).parent;
1005 void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
1007 // inherit properties from parent element
1008 charFormat = parent->charFormat;
1010 if (id == Html_html)
1011 blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
1012 else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
1013 blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
1015 if (parent->displayMode == QTextHtmlElement::DisplayNone)
1016 displayMode = QTextHtmlElement::DisplayNone;
1018 if (parent->id != Html_table || id == Html_caption) {
1019 if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
1020 blockFormat.setAlignment(parent->blockFormat.alignment());
1022 blockFormat.clearProperty(QTextFormat::BlockAlignment);
1024 // we don't paint per-row background colors, yet. so as an
1025 // exception inherit the background color here
1026 // we also inherit the background between inline elements
1027 if ((parent->id != Html_tr || !isTableCell())
1028 && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)) {
1029 charFormat.clearProperty(QTextFormat::BackgroundBrush);
1032 listStyle = parent->listStyle;
1033 // makes no sense to inherit that property, a named anchor is a single point
1034 // in the document, which is set by the DocumentFragment
1035 charFormat.clearProperty(QTextFormat::AnchorName);
1038 // initialize remaining properties
1039 margin[QTextHtmlParser::MarginLeft] = 0;
1040 margin[QTextHtmlParser::MarginRight] = 0;
1041 margin[QTextHtmlParser::MarginTop] = 0;
1042 margin[QTextHtmlParser::MarginBottom] = 0;
1043 cssFloat = QTextFrameFormat::InFlow;
1045 for (int i = 0; i < 4; ++i)
1048 // set element specific attributes
1051 charFormat.setAnchor(true);
1052 for (int i = 0; i < attributes.count(); i += 2) {
1053 const QString key = attributes.at(i);
1054 if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
1055 && !attributes.at(i + 1).isEmpty()) {
1057 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1058 charFormat.setForeground(QApplication::palette().link());
1069 charFormat.setFontItalic(true);
1072 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1075 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1079 charFormat.setFontWeight(QFont::Bold);
1082 charFormat.setFontWeight(QFont::Bold);
1083 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
1084 margin[QTextHtmlParser::MarginTop] = 18;
1085 margin[QTextHtmlParser::MarginBottom] = 12;
1088 charFormat.setFontWeight(QFont::Bold);
1089 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
1090 margin[QTextHtmlParser::MarginTop] = 16;
1091 margin[QTextHtmlParser::MarginBottom] = 12;
1094 charFormat.setFontWeight(QFont::Bold);
1095 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1096 margin[QTextHtmlParser::MarginTop] = 14;
1097 margin[QTextHtmlParser::MarginBottom] = 12;
1100 charFormat.setFontWeight(QFont::Bold);
1101 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
1102 margin[QTextHtmlParser::MarginTop] = 12;
1103 margin[QTextHtmlParser::MarginBottom] = 12;
1106 charFormat.setFontWeight(QFont::Bold);
1107 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1108 margin[QTextHtmlParser::MarginTop] = 12;
1109 margin[QTextHtmlParser::MarginBottom] = 4;
1112 margin[QTextHtmlParser::MarginTop] = 12;
1113 margin[QTextHtmlParser::MarginBottom] = 12;
1116 blockFormat.setAlignment(Qt::AlignCenter);
1119 listStyle = QTextListFormat::ListDisc;
1120 // nested lists don't have margins, except for the toplevel one
1121 if (!isNestedList(parser)) {
1122 margin[QTextHtmlParser::MarginTop] = 12;
1123 margin[QTextHtmlParser::MarginBottom] = 12;
1125 // no left margin as we use indenting instead
1128 listStyle = QTextListFormat::ListDecimal;
1129 // nested lists don't have margins, except for the toplevel one
1130 if (!isNestedList(parser)) {
1131 margin[QTextHtmlParser::MarginTop] = 12;
1132 margin[QTextHtmlParser::MarginBottom] = 12;
1134 // no left margin as we use indenting instead
1140 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1141 // <tt> uses a fixed font, so set the property
1142 charFormat.setFontFixedPitch(true);
1145 text = QChar(QChar::LineSeparator);
1146 wsm = QTextHtmlParserNode::WhiteSpacePre;
1150 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1151 wsm = WhiteSpacePre;
1152 margin[QTextHtmlParser::MarginTop] = 12;
1153 margin[QTextHtmlParser::MarginBottom] = 12;
1154 // <pre> uses a fixed font
1155 charFormat.setFontFixedPitch(true);
1157 case Html_blockquote:
1158 margin[QTextHtmlParser::MarginTop] = 12;
1159 margin[QTextHtmlParser::MarginBottom] = 12;
1160 margin[QTextHtmlParser::MarginLeft] = 40;
1161 margin[QTextHtmlParser::MarginRight] = 40;
1164 margin[QTextHtmlParser::MarginTop] = 8;
1165 margin[QTextHtmlParser::MarginBottom] = 8;
1168 margin[QTextHtmlParser::MarginLeft] = 30;
1171 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1174 charFormat.setFontStrikeOut(true);
1177 wsm = WhiteSpaceNoWrap;
1180 charFormat.setFontWeight(QFont::Bold);
1181 blockFormat.setAlignment(Qt::AlignCenter);
1184 blockFormat.setAlignment(Qt::AlignLeft);
1187 charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1190 charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1196 #ifndef QT_NO_CSSPARSER
1197 void QTextHtmlParserNode::setListStyle(const QVector<QCss::Value> &cssValues)
1199 for (int i = 0; i < cssValues.count(); ++i) {
1200 if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
1201 switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
1202 case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
1203 case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
1204 case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
1205 case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
1206 case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
1207 case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
1208 case QCss::Value_LowerRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerRoman; break;
1209 case QCss::Value_UpperRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperRoman; break;
1214 // allow individual list items to override the style
1215 if (id == Html_li && hasOwnListStyle)
1216 blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
1219 void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
1221 QCss::ValueExtractor extractor(declarations);
1222 extractor.extractBox(margin, padding);
1224 for (int i = 0; i < declarations.count(); ++i) {
1225 const QCss::Declaration &decl = declarations.at(i);
1226 if (decl.d->values.isEmpty()) continue;
1228 QCss::KnownValue identifier = QCss::UnknownValue;
1229 if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
1230 identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
1232 switch (decl.d->propertyId) {
1233 case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
1234 case QCss::BorderStyles:
1235 if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
1236 borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
1238 case QCss::BorderWidth:
1239 tableBorder = extractor.lengthValue(decl);
1241 case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
1243 cssFloat = QTextFrameFormat::InFlow;
1244 switch (identifier) {
1245 case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
1246 case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
1250 case QCss::QtBlockIndent:
1251 blockFormat.setIndent(decl.d->values.first().variant.toInt());
1253 case QCss::LineHeight: {
1255 if (decl.realValue(&lineHeight, "px")) {
1256 blockFormat.setLineHeight(lineHeight, QTextBlockFormat::FixedHeight);
1259 QString value = decl.d->values.first().toString();
1260 lineHeight = value.toDouble(&ok);
1262 blockFormat.setLineHeight(lineHeight, QTextBlockFormat::ProportionalHeight);
1264 blockFormat.setLineHeight(0, QTextBlockFormat::SingleHeight);
1267 case QCss::TextIndent: {
1269 if (decl.realValue(&indent, "px"))
1270 blockFormat.setTextIndent(indent);
1272 case QCss::QtListIndent:
1273 if (decl.intValue(&cssListIndent))
1274 hasCssListIndent = true;
1276 case QCss::QtParagraphType:
1277 if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
1278 isEmptyParagraph = true;
1280 case QCss::QtTableType:
1281 if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
1283 else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
1288 case QCss::QtUserState:
1289 userState = decl.d->values.first().variant.toInt();
1291 case QCss::Whitespace:
1292 switch (identifier) {
1293 case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
1294 case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
1295 case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
1296 case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
1300 case QCss::VerticalAlignment:
1301 switch (identifier) {
1302 case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
1303 case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
1304 case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
1305 case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
1306 case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
1307 default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
1310 case QCss::PageBreakBefore:
1311 switch (identifier) {
1312 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
1313 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
1317 case QCss::PageBreakAfter:
1318 switch (identifier) {
1319 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
1320 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
1324 case QCss::TextUnderlineStyle:
1325 switch (identifier) {
1326 case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
1327 case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
1328 case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
1329 case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
1330 case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
1331 case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
1332 case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
1336 case QCss::ListStyleType:
1337 case QCss::ListStyle:
1338 setListStyle(decl.d->values);
1340 case QCss::QtListNumberPrefix:
1341 textListNumberPrefix = decl.d->values.first().variant.toString();
1343 case QCss::QtListNumberSuffix:
1344 textListNumberSuffix = decl.d->values.first().variant.toString();
1351 int adjustment = -255;
1352 extractor.extractFont(&f, &adjustment);
1353 if (f.resolve() & QFont::SizeResolved) {
1354 if (f.pointSize() > 0) {
1355 charFormat.setFontPointSize(f.pointSize());
1356 } else if (f.pixelSize() > 0) {
1357 charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
1360 if (f.resolve() & QFont::StyleResolved)
1361 charFormat.setFontItalic(f.style() != QFont::StyleNormal);
1363 if (f.resolve() & QFont::WeightResolved)
1364 charFormat.setFontWeight(f.weight());
1366 if (f.resolve() & QFont::FamilyResolved)
1367 charFormat.setFontFamily(f.family());
1369 if (f.resolve() & QFont::UnderlineResolved)
1370 charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
1372 if (f.resolve() & QFont::OverlineResolved)
1373 charFormat.setFontOverline(f.overline());
1375 if (f.resolve() & QFont::StrikeOutResolved)
1376 charFormat.setFontStrikeOut(f.strikeOut());
1378 if (f.resolve() & QFont::CapitalizationResolved)
1379 charFormat.setFontCapitalization(f.capitalization());
1381 if (adjustment >= -1)
1382 charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
1385 Qt::Alignment ignoredAlignment;
1386 QCss::Repeat ignoredRepeat;
1389 QCss::Origin ignoredOrigin, ignoredClip;
1390 QCss::Attachment ignoredAttachment;
1391 extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
1392 &ignoredOrigin, &ignoredAttachment, &ignoredClip);
1394 if (!bgImage.isEmpty() && resourceProvider) {
1395 applyBackgroundImage(bgImage, resourceProvider);
1396 } else if (bgBrush.style() != Qt::NoBrush) {
1397 charFormat.setBackground(bgBrush);
1402 #endif // QT_NO_CSSPARSER
1404 void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
1406 if (!url.isEmpty() && resourceProvider) {
1407 QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
1409 if (qApp->thread() != QThread::currentThread()) {
1410 // must use images in non-GUI threads
1411 if (val.type() == QVariant::Image) {
1412 QImage image = qvariant_cast<QImage>(val);
1413 charFormat.setBackground(image);
1414 } else if (val.type() == QVariant::ByteArray) {
1416 if (image.loadFromData(val.toByteArray())) {
1417 charFormat.setBackground(image);
1421 if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
1422 charFormat.setBackground(qvariant_cast<QPixmap>(val));
1423 } else if (val.type() == QVariant::ByteArray) {
1425 if (pm.loadFromData(val.toByteArray())) {
1426 charFormat.setBackground(pm);
1432 charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
1435 bool QTextHtmlParserNode::hasOnlyWhitespace() const
1437 for (int i = 0; i < text.count(); ++i)
1438 if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
1443 static bool setIntAttribute(int *destination, const QString &value)
1446 int val = value.toInt(&ok);
1453 static bool setFloatAttribute(qreal *destination, const QString &value)
1456 qreal val = value.toDouble(&ok);
1463 static void setWidthAttribute(QTextLength *width, QString value)
1466 qreal realVal = value.toDouble(&ok);
1468 *width = QTextLength(QTextLength::FixedLength, realVal);
1470 value = value.trimmed();
1471 if (!value.isEmpty() && value.endsWith(QLatin1Char('%'))) {
1473 realVal = value.toDouble(&ok);
1475 *width = QTextLength(QTextLength::PercentageLength, realVal);
1480 #ifndef QT_NO_CSSPARSER
1481 void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
1483 QString css = value;
1484 css.prepend(QLatin1String("* {"));
1485 css.append(QLatin1Char('}'));
1486 QCss::Parser parser(css);
1487 QCss::StyleSheet sheet;
1488 parser.parse(&sheet, Qt::CaseInsensitive);
1489 if (sheet.styleRules.count() != 1) return;
1490 applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
1494 QStringList QTextHtmlParser::parseAttributes()
1500 if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
1502 QString key = parseWord().toLower();
1503 QString value = QLatin1String("1");
1504 if (key.size() == 0)
1507 if (hasPrefix(QLatin1Char('='))){
1510 value = parseWord();
1512 if (value.size() == 0)
1514 attrs << key << value;
1520 void QTextHtmlParser::applyAttributes(const QStringList &attributes)
1522 // local state variable for qt3 textedit mode
1523 bool seenQt3Richtext = false;
1527 if (attributes.count() % 2 == 1)
1530 QTextHtmlParserNode *node = &nodes.last();
1532 for (int i = 0; i < attributes.count(); i += 2) {
1533 QString key = attributes.at(i);
1534 QString value = attributes.at(i + 1);
1538 // the infamous font tag
1539 if (key == QLatin1String("size") && value.size()) {
1540 int n = value.toInt();
1541 if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
1543 node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
1544 } else if (key == QLatin1String("face")) {
1545 node->charFormat.setFontFamily(value);
1546 } else if (key == QLatin1String("color")) {
1547 QColor c; c.setNamedColor(value);
1549 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1550 node->charFormat.setForeground(c);
1555 if (key == QLatin1String("type")) {
1556 node->hasOwnListStyle = true;
1557 if (value == QLatin1String("1")) {
1558 node->listStyle = QTextListFormat::ListDecimal;
1559 } else if (value == QLatin1String("a")) {
1560 node->listStyle = QTextListFormat::ListLowerAlpha;
1561 } else if (value == QLatin1String("A")) {
1562 node->listStyle = QTextListFormat::ListUpperAlpha;
1563 } else if (value == QLatin1String("i")) {
1564 node->listStyle = QTextListFormat::ListLowerRoman;
1565 } else if (value == QLatin1String("I")) {
1566 node->listStyle = QTextListFormat::ListUpperRoman;
1568 value = value.toLower();
1569 if (value == QLatin1String("square"))
1570 node->listStyle = QTextListFormat::ListSquare;
1571 else if (value == QLatin1String("disc"))
1572 node->listStyle = QTextListFormat::ListDisc;
1573 else if (value == QLatin1String("circle"))
1574 node->listStyle = QTextListFormat::ListCircle;
1579 if (key == QLatin1String("href"))
1580 node->charFormat.setAnchorHref(value);
1581 else if (key == QLatin1String("name"))
1582 node->charFormat.setAnchorName(value);
1585 if (key == QLatin1String("src") || key == QLatin1String("source")) {
1586 node->imageName = value;
1587 } else if (key == QLatin1String("width")) {
1588 node->imageWidth = -2; // register that there is a value for it.
1589 setFloatAttribute(&node->imageWidth, value);
1590 } else if (key == QLatin1String("height")) {
1591 node->imageHeight = -2; // register that there is a value for it.
1592 setFloatAttribute(&node->imageHeight, value);
1597 if (key == QLatin1String("bgcolor")) {
1598 QColor c; c.setNamedColor(value);
1600 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1601 node->charFormat.setBackground(c);
1602 } else if (key == QLatin1String("background")) {
1603 node->applyBackgroundImage(value, resourceProvider);
1608 if (key == QLatin1String("width")) {
1609 setWidthAttribute(&node->width, value);
1610 } else if (key == QLatin1String("bgcolor")) {
1611 QColor c; c.setNamedColor(value);
1613 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1614 node->charFormat.setBackground(c);
1615 } else if (key == QLatin1String("background")) {
1616 node->applyBackgroundImage(value, resourceProvider);
1617 } else if (key == QLatin1String("rowspan")) {
1618 if (setIntAttribute(&node->tableCellRowSpan, value))
1619 node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
1620 } else if (key == QLatin1String("colspan")) {
1621 if (setIntAttribute(&node->tableCellColSpan, value))
1622 node->tableCellColSpan = qMax(1, node->tableCellColSpan);
1626 if (key == QLatin1String("border")) {
1627 setFloatAttribute(&node->tableBorder, value);
1628 } else if (key == QLatin1String("bgcolor")) {
1629 QColor c; c.setNamedColor(value);
1631 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1632 node->charFormat.setBackground(c);
1633 } else if (key == QLatin1String("background")) {
1634 node->applyBackgroundImage(value, resourceProvider);
1635 } else if (key == QLatin1String("cellspacing")) {
1636 setFloatAttribute(&node->tableCellSpacing, value);
1637 } else if (key == QLatin1String("cellpadding")) {
1638 setFloatAttribute(&node->tableCellPadding, value);
1639 } else if (key == QLatin1String("width")) {
1640 setWidthAttribute(&node->width, value);
1641 } else if (key == QLatin1String("height")) {
1642 setWidthAttribute(&node->height, value);
1646 if (key == QLatin1String("name")
1647 && value == QLatin1String("qrichtext")) {
1648 seenQt3Richtext = true;
1651 if (key == QLatin1String("content")
1652 && value == QLatin1String("1")
1653 && seenQt3Richtext) {
1655 textEditMode = true;
1659 if (key == QLatin1String("width"))
1660 setWidthAttribute(&node->width, value);
1663 if (key == QLatin1String("href"))
1665 else if (key == QLatin1String("type"))
1672 if (key == QLatin1String("style")) {
1673 #ifndef QT_NO_CSSPARSER
1674 node->parseStyleAttribute(value, resourceProvider);
1676 } else if (key == QLatin1String("align")) {
1677 value = value.toLower();
1678 bool alignmentSet = true;
1680 if (value == QLatin1String("left"))
1681 node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
1682 else if (value == QLatin1String("right"))
1683 node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
1684 else if (value == QLatin1String("center"))
1685 node->blockFormat.setAlignment(Qt::AlignHCenter);
1686 else if (value == QLatin1String("justify"))
1687 node->blockFormat.setAlignment(Qt::AlignJustify);
1689 alignmentSet = false;
1691 if (node->id == Html_img) {
1694 if (node->blockFormat.alignment() & Qt::AlignLeft)
1695 node->cssFloat = QTextFrameFormat::FloatLeft;
1696 else if (node->blockFormat.alignment() & Qt::AlignRight)
1697 node->cssFloat = QTextFrameFormat::FloatRight;
1698 } else if (value == QLatin1String("middle")) {
1699 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1700 } else if (value == QLatin1String("top")) {
1701 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1704 } else if (key == QLatin1String("valign")) {
1705 value = value.toLower();
1706 if (value == QLatin1String("top"))
1707 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1708 else if (value == QLatin1String("middle"))
1709 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1710 else if (value == QLatin1String("bottom"))
1711 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
1712 } else if (key == QLatin1String("dir")) {
1713 value = value.toLower();
1714 if (value == QLatin1String("ltr"))
1715 node->blockFormat.setLayoutDirection(Qt::LeftToRight);
1716 else if (value == QLatin1String("rtl"))
1717 node->blockFormat.setLayoutDirection(Qt::RightToLeft);
1718 } else if (key == QLatin1String("title")) {
1719 node->charFormat.setToolTip(value);
1720 } else if (key == QLatin1String("id")) {
1721 node->charFormat.setAnchor(true);
1722 node->charFormat.setAnchorName(value);
1726 #ifndef QT_NO_CSSPARSER
1727 if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
1728 importStyleSheet(linkHref);
1732 #ifndef QT_NO_CSSPARSER
1733 class QTextHtmlStyleSelector : public QCss::StyleSelector
1736 inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
1737 : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
1739 virtual QStringList nodeNames(NodePtr node) const;
1740 virtual QString attribute(NodePtr node, const QString &name) const;
1741 virtual bool hasAttributes(NodePtr node) const;
1742 virtual bool isNullNode(NodePtr node) const;
1743 virtual NodePtr parentNode(NodePtr node) const;
1744 virtual NodePtr previousSiblingNode(NodePtr node) const;
1745 virtual NodePtr duplicateNode(NodePtr node) const;
1746 virtual void freeNode(NodePtr node) const;
1749 const QTextHtmlParser *parser;
1752 QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
1754 return QStringList(parser->at(node.id).tag.toLower());
1757 #endif // QT_NO_CSSPARSER
1759 static inline int findAttribute(const QStringList &attributes, const QString &name)
1763 idx = attributes.indexOf(name, idx + 1);
1764 } while (idx != -1 && (idx % 2 == 1));
1768 #ifndef QT_NO_CSSPARSER
1770 QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
1772 const QStringList &attributes = parser->at(node.id).attributes;
1773 const int idx = findAttribute(attributes, name);
1776 return attributes.at(idx + 1);
1779 bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
1781 const QStringList &attributes = parser->at(node.id).attributes;
1782 return !attributes.isEmpty();
1785 bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
1787 return node.id == 0;
1790 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
1795 parent.id = parser->at(node.id).parent;
1800 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
1805 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
1811 int parent = parser->at(node.id).parent;
1814 const int childIdx = parser->at(parent).children.indexOf(node.id);
1817 sibling.id = parser->at(parent).children.at(childIdx - 1);
1821 void QTextHtmlStyleSelector::freeNode(NodePtr) const
1825 void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
1827 for (int i = 0; i < sheet.importRules.count(); ++i) {
1828 const QCss::ImportRule &rule = sheet.importRules.at(i);
1829 if (rule.media.isEmpty()
1830 || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
1831 importStyleSheet(rule.href);
1835 void QTextHtmlParser::importStyleSheet(const QString &href)
1837 if (!resourceProvider)
1839 for (int i = 0; i < externalStyleSheets.count(); ++i)
1840 if (externalStyleSheets.at(i).url == href)
1843 QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
1845 if (res.type() == QVariant::String) {
1846 css = res.toString();
1847 } else if (res.type() == QVariant::ByteArray) {
1848 // #### detect @charset
1849 css = QString::fromUtf8(res.toByteArray());
1851 if (!css.isEmpty()) {
1852 QCss::Parser parser(css);
1853 QCss::StyleSheet sheet;
1854 parser.parse(&sheet, Qt::CaseInsensitive);
1855 externalStyleSheets.append(ExternalStyleSheet(href, sheet));
1856 resolveStyleSheetImports(sheet);
1860 QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
1862 QVector<QCss::Declaration> decls;
1864 QTextHtmlStyleSelector selector(this);
1867 selector.styleSheets.resize((resourceProvider ? 1 : 0)
1868 + externalStyleSheets.count()
1869 + inlineStyleSheets.count());
1870 if (resourceProvider)
1871 selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
1873 for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
1874 selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
1876 for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
1877 selector.styleSheets[idx] = inlineStyleSheets.at(i);
1879 selector.medium = QLatin1String("screen");
1881 QCss::StyleSelector::NodePtr n;
1884 const char *extraPseudo = 0;
1885 if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
1886 extraPseudo = "link";
1887 decls = selector.declarationsForNode(n, extraPseudo);
1892 bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
1903 #endif // QT_NO_CSSPARSER
1905 #endif // QT_NO_TEXTHTMLPARSER