1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtexthtmlparser_p.h"
44 #include <qbytearray.h>
45 #include <qtextcodec.h>
49 #include <qcoreapplication.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];
823 if (QChar::requiresSurrogates(uc)) {
824 str += QChar(QChar::highSurrogate(uc));
825 str += QChar(QChar::lowSurrogate(uc));
834 return QLatin1String("&");
837 // parses one word, possibly quoted, and returns it
838 QString QTextHtmlParser::parseWord()
841 if (hasPrefix(QLatin1Char('\"'))) { // double quotes
844 QChar c = txt.at(pos++);
845 if (c == QLatin1Char('\"'))
847 else if (c == QLatin1Char('&'))
848 word += parseEntity();
852 } else if (hasPrefix(QLatin1Char('\''))) { // single quotes
855 QChar c = txt.at(pos++);
856 if (c == QLatin1Char('\''))
861 } else { // normal text
863 QChar c = txt.at(pos++);
864 if (c == QLatin1Char('>')
865 || (c == QLatin1Char('/') && hasPrefix(QLatin1Char('>'), 1))
866 || c == QLatin1Char('<')
867 || c == QLatin1Char('=')
872 if (c == QLatin1Char('&'))
873 word += parseEntity();
881 // gives the new node the right parent
882 QTextHtmlParserNode *QTextHtmlParser::resolveParent()
884 QTextHtmlParserNode *node = &nodes.last();
886 int p = node->parent;
888 // Excel gives us buggy HTML with just tr without surrounding table tags
889 // or with just td tags
891 if (node->id == Html_td) {
893 while (n && at(n).id != Html_tr)
897 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
898 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
900 QTextHtmlParserNode *table = &nodes[nodes.count() - 3];
902 table->id = Html_table;
903 table->tag = QLatin1String("table");
904 table->children.append(nodes.count() - 2); // add row as child
906 QTextHtmlParserNode *row = &nodes[nodes.count() - 2];
907 row->parent = nodes.count() - 3; // table as parent
909 row->tag = QLatin1String("tr");
911 p = nodes.count() - 2;
912 node = &nodes.last(); // re-initialize pointer
916 if (node->id == Html_tr) {
918 while (n && at(n).id != Html_table)
922 nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
923 QTextHtmlParserNode *table = &nodes[nodes.count() - 2];
925 table->id = Html_table;
926 table->tag = QLatin1String("table");
927 p = nodes.count() - 2;
928 node = &nodes.last(); // re-initialize pointer
932 // permit invalid html by letting block elements be children
933 // of inline elements with the exception of paragraphs:
935 // a new paragraph closes parent inline elements (while loop),
936 // unless they themselves are children of a non-paragraph block
937 // element (if statement)
941 // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
942 // belongs to the first <p>. The self-nesting
943 // check further down prevents the second <p>
944 // from nesting into the first one then.
945 // so Bar is not bold.
947 // <body><b><p>Foo <-- Foo should be bold.
949 // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
951 if (node->id == Html_p) {
952 while (p && !at(p).isBlock())
955 if (!p || at(p).id != Html_p)
959 // some elements are not self nesting
960 if (node->id == at(p).id
961 && node->isNotSelfNesting())
964 // some elements are not allowed in certain contexts
965 while ((p && !node->allowedInContext(at(p).id))
966 // ### make new styles aware of empty tags
967 || at(p).mayNotHaveChildren()
974 // makes it easier to traverse the tree, later
975 nodes[p].children.append(nodes.count() - 1);
979 // sets all properties on the new node
980 void QTextHtmlParser::resolveNode()
982 QTextHtmlParserNode *node = &nodes.last();
983 const QTextHtmlParserNode *parent = &nodes.at(node->parent);
984 node->initializeProperties(parent, this);
987 bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
994 if (parser->at(p).isListStart())
996 p = parser->at(p).parent;
1001 void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
1003 // inherit properties from parent element
1004 charFormat = parent->charFormat;
1006 if (id == Html_html)
1007 blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
1008 else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
1009 blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
1011 if (parent->displayMode == QTextHtmlElement::DisplayNone)
1012 displayMode = QTextHtmlElement::DisplayNone;
1014 if (parent->id != Html_table || id == Html_caption) {
1015 if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
1016 blockFormat.setAlignment(parent->blockFormat.alignment());
1018 blockFormat.clearProperty(QTextFormat::BlockAlignment);
1020 // we don't paint per-row background colors, yet. so as an
1021 // exception inherit the background color here
1022 // we also inherit the background between inline elements
1023 if ((parent->id != Html_tr || !isTableCell())
1024 && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)) {
1025 charFormat.clearProperty(QTextFormat::BackgroundBrush);
1028 listStyle = parent->listStyle;
1029 // makes no sense to inherit that property, a named anchor is a single point
1030 // in the document, which is set by the DocumentFragment
1031 charFormat.clearProperty(QTextFormat::AnchorName);
1034 // initialize remaining properties
1035 margin[QTextHtmlParser::MarginLeft] = 0;
1036 margin[QTextHtmlParser::MarginRight] = 0;
1037 margin[QTextHtmlParser::MarginTop] = 0;
1038 margin[QTextHtmlParser::MarginBottom] = 0;
1039 cssFloat = QTextFrameFormat::InFlow;
1041 for (int i = 0; i < 4; ++i)
1044 // set element specific attributes
1047 charFormat.setAnchor(true);
1048 for (int i = 0; i < attributes.count(); i += 2) {
1049 const QString key = attributes.at(i);
1050 if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
1051 && !attributes.at(i + 1).isEmpty()) {
1053 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1054 charFormat.setForeground(Qt::blue);
1065 charFormat.setFontItalic(true);
1068 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1071 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1075 charFormat.setFontWeight(QFont::Bold);
1078 charFormat.setFontWeight(QFont::Bold);
1079 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
1080 margin[QTextHtmlParser::MarginTop] = 18;
1081 margin[QTextHtmlParser::MarginBottom] = 12;
1084 charFormat.setFontWeight(QFont::Bold);
1085 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
1086 margin[QTextHtmlParser::MarginTop] = 16;
1087 margin[QTextHtmlParser::MarginBottom] = 12;
1090 charFormat.setFontWeight(QFont::Bold);
1091 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
1092 margin[QTextHtmlParser::MarginTop] = 14;
1093 margin[QTextHtmlParser::MarginBottom] = 12;
1096 charFormat.setFontWeight(QFont::Bold);
1097 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
1098 margin[QTextHtmlParser::MarginTop] = 12;
1099 margin[QTextHtmlParser::MarginBottom] = 12;
1102 charFormat.setFontWeight(QFont::Bold);
1103 charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
1104 margin[QTextHtmlParser::MarginTop] = 12;
1105 margin[QTextHtmlParser::MarginBottom] = 4;
1108 margin[QTextHtmlParser::MarginTop] = 12;
1109 margin[QTextHtmlParser::MarginBottom] = 12;
1112 blockFormat.setAlignment(Qt::AlignCenter);
1115 listStyle = QTextListFormat::ListDisc;
1116 // nested lists don't have margins, except for the toplevel one
1117 if (!isNestedList(parser)) {
1118 margin[QTextHtmlParser::MarginTop] = 12;
1119 margin[QTextHtmlParser::MarginBottom] = 12;
1121 // no left margin as we use indenting instead
1124 listStyle = QTextListFormat::ListDecimal;
1125 // nested lists don't have margins, except for the toplevel one
1126 if (!isNestedList(parser)) {
1127 margin[QTextHtmlParser::MarginTop] = 12;
1128 margin[QTextHtmlParser::MarginBottom] = 12;
1130 // no left margin as we use indenting instead
1136 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1137 // <tt> uses a fixed font, so set the property
1138 charFormat.setFontFixedPitch(true);
1141 text = QChar(QChar::LineSeparator);
1142 wsm = QTextHtmlParserNode::WhiteSpacePre;
1146 charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
1147 wsm = WhiteSpacePre;
1148 margin[QTextHtmlParser::MarginTop] = 12;
1149 margin[QTextHtmlParser::MarginBottom] = 12;
1150 // <pre> uses a fixed font
1151 charFormat.setFontFixedPitch(true);
1153 case Html_blockquote:
1154 margin[QTextHtmlParser::MarginTop] = 12;
1155 margin[QTextHtmlParser::MarginBottom] = 12;
1156 margin[QTextHtmlParser::MarginLeft] = 40;
1157 margin[QTextHtmlParser::MarginRight] = 40;
1160 margin[QTextHtmlParser::MarginTop] = 8;
1161 margin[QTextHtmlParser::MarginBottom] = 8;
1164 margin[QTextHtmlParser::MarginLeft] = 30;
1167 charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
1170 charFormat.setFontStrikeOut(true);
1173 wsm = WhiteSpaceNoWrap;
1176 charFormat.setFontWeight(QFont::Bold);
1177 blockFormat.setAlignment(Qt::AlignCenter);
1180 blockFormat.setAlignment(Qt::AlignLeft);
1183 charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1186 charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1192 #ifndef QT_NO_CSSPARSER
1193 void QTextHtmlParserNode::setListStyle(const QVector<QCss::Value> &cssValues)
1195 for (int i = 0; i < cssValues.count(); ++i) {
1196 if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
1197 switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
1198 case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
1199 case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
1200 case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
1201 case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
1202 case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
1203 case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
1204 case QCss::Value_LowerRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerRoman; break;
1205 case QCss::Value_UpperRoman: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperRoman; break;
1210 // allow individual list items to override the style
1211 if (id == Html_li && hasOwnListStyle)
1212 blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
1215 void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
1217 QCss::ValueExtractor extractor(declarations);
1218 extractor.extractBox(margin, padding);
1220 for (int i = 0; i < declarations.count(); ++i) {
1221 const QCss::Declaration &decl = declarations.at(i);
1222 if (decl.d->values.isEmpty()) continue;
1224 QCss::KnownValue identifier = QCss::UnknownValue;
1225 if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
1226 identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
1228 switch (decl.d->propertyId) {
1229 case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
1230 case QCss::BorderStyles:
1231 if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
1232 borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
1234 case QCss::BorderWidth:
1235 tableBorder = extractor.lengthValue(decl);
1237 case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
1239 cssFloat = QTextFrameFormat::InFlow;
1240 switch (identifier) {
1241 case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
1242 case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
1246 case QCss::QtBlockIndent:
1247 blockFormat.setIndent(decl.d->values.first().variant.toInt());
1249 case QCss::LineHeight: {
1251 if (decl.realValue(&lineHeight, "px")) {
1252 blockFormat.setLineHeight(lineHeight, QTextBlockFormat::FixedHeight);
1255 QString value = decl.d->values.first().toString();
1256 lineHeight = value.toDouble(&ok);
1258 blockFormat.setLineHeight(lineHeight, QTextBlockFormat::ProportionalHeight);
1260 blockFormat.setLineHeight(0, QTextBlockFormat::SingleHeight);
1263 case QCss::TextIndent: {
1265 if (decl.realValue(&indent, "px"))
1266 blockFormat.setTextIndent(indent);
1268 case QCss::QtListIndent:
1269 if (decl.intValue(&cssListIndent))
1270 hasCssListIndent = true;
1272 case QCss::QtParagraphType:
1273 if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
1274 isEmptyParagraph = true;
1276 case QCss::QtTableType:
1277 if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
1279 else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
1284 case QCss::QtUserState:
1285 userState = decl.d->values.first().variant.toInt();
1287 case QCss::Whitespace:
1288 switch (identifier) {
1289 case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
1290 case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
1291 case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
1292 case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
1296 case QCss::VerticalAlignment:
1297 switch (identifier) {
1298 case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
1299 case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
1300 case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
1301 case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
1302 case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
1303 default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
1306 case QCss::PageBreakBefore:
1307 switch (identifier) {
1308 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
1309 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
1313 case QCss::PageBreakAfter:
1314 switch (identifier) {
1315 case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
1316 case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
1320 case QCss::TextUnderlineStyle:
1321 switch (identifier) {
1322 case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
1323 case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
1324 case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
1325 case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
1326 case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
1327 case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
1328 case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
1332 case QCss::ListStyleType:
1333 case QCss::ListStyle:
1334 setListStyle(decl.d->values);
1336 case QCss::QtListNumberPrefix:
1337 textListNumberPrefix = decl.d->values.first().variant.toString();
1339 case QCss::QtListNumberSuffix:
1340 textListNumberSuffix = decl.d->values.first().variant.toString();
1347 int adjustment = -255;
1348 extractor.extractFont(&f, &adjustment);
1349 if (f.resolve() & QFont::SizeResolved) {
1350 if (f.pointSize() > 0) {
1351 charFormat.setFontPointSize(f.pointSize());
1352 } else if (f.pixelSize() > 0) {
1353 charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
1356 if (f.resolve() & QFont::StyleResolved)
1357 charFormat.setFontItalic(f.style() != QFont::StyleNormal);
1359 if (f.resolve() & QFont::WeightResolved)
1360 charFormat.setFontWeight(f.weight());
1362 if (f.resolve() & QFont::FamilyResolved)
1363 charFormat.setFontFamily(f.family());
1365 if (f.resolve() & QFont::UnderlineResolved)
1366 charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
1368 if (f.resolve() & QFont::OverlineResolved)
1369 charFormat.setFontOverline(f.overline());
1371 if (f.resolve() & QFont::StrikeOutResolved)
1372 charFormat.setFontStrikeOut(f.strikeOut());
1374 if (f.resolve() & QFont::CapitalizationResolved)
1375 charFormat.setFontCapitalization(f.capitalization());
1377 if (adjustment >= -1)
1378 charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
1381 Qt::Alignment ignoredAlignment;
1382 QCss::Repeat ignoredRepeat;
1385 QCss::Origin ignoredOrigin, ignoredClip;
1386 QCss::Attachment ignoredAttachment;
1387 extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
1388 &ignoredOrigin, &ignoredAttachment, &ignoredClip);
1390 if (!bgImage.isEmpty() && resourceProvider) {
1391 applyBackgroundImage(bgImage, resourceProvider);
1392 } else if (bgBrush.style() != Qt::NoBrush) {
1393 charFormat.setBackground(bgBrush);
1398 #endif // QT_NO_CSSPARSER
1400 void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
1402 if (!url.isEmpty() && resourceProvider) {
1403 QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
1405 if (QCoreApplication::instance()->thread() != QThread::currentThread()) {
1406 // must use images in non-GUI threads
1407 if (val.type() == QVariant::Image) {
1408 QImage image = qvariant_cast<QImage>(val);
1409 charFormat.setBackground(image);
1410 } else if (val.type() == QVariant::ByteArray) {
1412 if (image.loadFromData(val.toByteArray())) {
1413 charFormat.setBackground(image);
1417 if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
1418 charFormat.setBackground(qvariant_cast<QPixmap>(val));
1419 } else if (val.type() == QVariant::ByteArray) {
1421 if (pm.loadFromData(val.toByteArray())) {
1422 charFormat.setBackground(pm);
1428 charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
1431 bool QTextHtmlParserNode::hasOnlyWhitespace() const
1433 for (int i = 0; i < text.count(); ++i)
1434 if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
1439 static bool setIntAttribute(int *destination, const QString &value)
1442 int val = value.toInt(&ok);
1449 static bool setFloatAttribute(qreal *destination, const QString &value)
1452 qreal val = value.toDouble(&ok);
1459 static void setWidthAttribute(QTextLength *width, QString value)
1462 qreal realVal = value.toDouble(&ok);
1464 *width = QTextLength(QTextLength::FixedLength, realVal);
1466 value = value.trimmed();
1467 if (!value.isEmpty() && value.endsWith(QLatin1Char('%'))) {
1469 realVal = value.toDouble(&ok);
1471 *width = QTextLength(QTextLength::PercentageLength, realVal);
1476 #ifndef QT_NO_CSSPARSER
1477 void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
1479 QString css = value;
1480 css.prepend(QLatin1String("* {"));
1481 css.append(QLatin1Char('}'));
1482 QCss::Parser parser(css);
1483 QCss::StyleSheet sheet;
1484 parser.parse(&sheet, Qt::CaseInsensitive);
1485 if (sheet.styleRules.count() != 1) return;
1486 applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
1490 QStringList QTextHtmlParser::parseAttributes()
1496 if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
1498 QString key = parseWord().toLower();
1499 QString value = QLatin1String("1");
1500 if (key.size() == 0)
1503 if (hasPrefix(QLatin1Char('='))){
1506 value = parseWord();
1508 if (value.size() == 0)
1510 attrs << key << value;
1516 void QTextHtmlParser::applyAttributes(const QStringList &attributes)
1518 // local state variable for qt3 textedit mode
1519 bool seenQt3Richtext = false;
1523 if (attributes.count() % 2 == 1)
1526 QTextHtmlParserNode *node = &nodes.last();
1528 for (int i = 0; i < attributes.count(); i += 2) {
1529 QString key = attributes.at(i);
1530 QString value = attributes.at(i + 1);
1534 // the infamous font tag
1535 if (key == QLatin1String("size") && value.size()) {
1536 int n = value.toInt();
1537 if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
1539 node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
1540 } else if (key == QLatin1String("face")) {
1541 node->charFormat.setFontFamily(value);
1542 } else if (key == QLatin1String("color")) {
1543 QColor c; c.setNamedColor(value);
1545 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1546 node->charFormat.setForeground(c);
1551 if (key == QLatin1String("type")) {
1552 node->hasOwnListStyle = true;
1553 if (value == QLatin1String("1")) {
1554 node->listStyle = QTextListFormat::ListDecimal;
1555 } else if (value == QLatin1String("a")) {
1556 node->listStyle = QTextListFormat::ListLowerAlpha;
1557 } else if (value == QLatin1String("A")) {
1558 node->listStyle = QTextListFormat::ListUpperAlpha;
1559 } else if (value == QLatin1String("i")) {
1560 node->listStyle = QTextListFormat::ListLowerRoman;
1561 } else if (value == QLatin1String("I")) {
1562 node->listStyle = QTextListFormat::ListUpperRoman;
1564 value = value.toLower();
1565 if (value == QLatin1String("square"))
1566 node->listStyle = QTextListFormat::ListSquare;
1567 else if (value == QLatin1String("disc"))
1568 node->listStyle = QTextListFormat::ListDisc;
1569 else if (value == QLatin1String("circle"))
1570 node->listStyle = QTextListFormat::ListCircle;
1575 if (key == QLatin1String("href"))
1576 node->charFormat.setAnchorHref(value);
1577 else if (key == QLatin1String("name"))
1578 node->charFormat.setAnchorName(value);
1581 if (key == QLatin1String("src") || key == QLatin1String("source")) {
1582 node->imageName = value;
1583 } else if (key == QLatin1String("width")) {
1584 node->imageWidth = -2; // register that there is a value for it.
1585 setFloatAttribute(&node->imageWidth, value);
1586 } else if (key == QLatin1String("height")) {
1587 node->imageHeight = -2; // register that there is a value for it.
1588 setFloatAttribute(&node->imageHeight, value);
1593 if (key == QLatin1String("bgcolor")) {
1594 QColor c; c.setNamedColor(value);
1596 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1597 node->charFormat.setBackground(c);
1598 } else if (key == QLatin1String("background")) {
1599 node->applyBackgroundImage(value, resourceProvider);
1604 if (key == QLatin1String("width")) {
1605 setWidthAttribute(&node->width, value);
1606 } else if (key == QLatin1String("bgcolor")) {
1607 QColor c; c.setNamedColor(value);
1609 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1610 node->charFormat.setBackground(c);
1611 } else if (key == QLatin1String("background")) {
1612 node->applyBackgroundImage(value, resourceProvider);
1613 } else if (key == QLatin1String("rowspan")) {
1614 if (setIntAttribute(&node->tableCellRowSpan, value))
1615 node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
1616 } else if (key == QLatin1String("colspan")) {
1617 if (setIntAttribute(&node->tableCellColSpan, value))
1618 node->tableCellColSpan = qMax(1, node->tableCellColSpan);
1622 if (key == QLatin1String("border")) {
1623 setFloatAttribute(&node->tableBorder, value);
1624 } else if (key == QLatin1String("bgcolor")) {
1625 QColor c; c.setNamedColor(value);
1627 qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
1628 node->charFormat.setBackground(c);
1629 } else if (key == QLatin1String("background")) {
1630 node->applyBackgroundImage(value, resourceProvider);
1631 } else if (key == QLatin1String("cellspacing")) {
1632 setFloatAttribute(&node->tableCellSpacing, value);
1633 } else if (key == QLatin1String("cellpadding")) {
1634 setFloatAttribute(&node->tableCellPadding, value);
1635 } else if (key == QLatin1String("width")) {
1636 setWidthAttribute(&node->width, value);
1637 } else if (key == QLatin1String("height")) {
1638 setWidthAttribute(&node->height, value);
1642 if (key == QLatin1String("name")
1643 && value == QLatin1String("qrichtext")) {
1644 seenQt3Richtext = true;
1647 if (key == QLatin1String("content")
1648 && value == QLatin1String("1")
1649 && seenQt3Richtext) {
1651 textEditMode = true;
1655 if (key == QLatin1String("width"))
1656 setWidthAttribute(&node->width, value);
1659 if (key == QLatin1String("href"))
1661 else if (key == QLatin1String("type"))
1668 if (key == QLatin1String("style")) {
1669 #ifndef QT_NO_CSSPARSER
1670 node->parseStyleAttribute(value, resourceProvider);
1672 } else if (key == QLatin1String("align")) {
1673 value = value.toLower();
1674 bool alignmentSet = true;
1676 if (value == QLatin1String("left"))
1677 node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
1678 else if (value == QLatin1String("right"))
1679 node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
1680 else if (value == QLatin1String("center"))
1681 node->blockFormat.setAlignment(Qt::AlignHCenter);
1682 else if (value == QLatin1String("justify"))
1683 node->blockFormat.setAlignment(Qt::AlignJustify);
1685 alignmentSet = false;
1687 if (node->id == Html_img) {
1690 if (node->blockFormat.alignment() & Qt::AlignLeft)
1691 node->cssFloat = QTextFrameFormat::FloatLeft;
1692 else if (node->blockFormat.alignment() & Qt::AlignRight)
1693 node->cssFloat = QTextFrameFormat::FloatRight;
1694 } else if (value == QLatin1String("middle")) {
1695 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1696 } else if (value == QLatin1String("top")) {
1697 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1700 } else if (key == QLatin1String("valign")) {
1701 value = value.toLower();
1702 if (value == QLatin1String("top"))
1703 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
1704 else if (value == QLatin1String("middle"))
1705 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1706 else if (value == QLatin1String("bottom"))
1707 node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
1708 } else if (key == QLatin1String("dir")) {
1709 value = value.toLower();
1710 if (value == QLatin1String("ltr"))
1711 node->blockFormat.setLayoutDirection(Qt::LeftToRight);
1712 else if (value == QLatin1String("rtl"))
1713 node->blockFormat.setLayoutDirection(Qt::RightToLeft);
1714 } else if (key == QLatin1String("title")) {
1715 node->charFormat.setToolTip(value);
1716 } else if (key == QLatin1String("id")) {
1717 node->charFormat.setAnchor(true);
1718 node->charFormat.setAnchorName(value);
1722 #ifndef QT_NO_CSSPARSER
1723 if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
1724 importStyleSheet(linkHref);
1728 #ifndef QT_NO_CSSPARSER
1729 class QTextHtmlStyleSelector : public QCss::StyleSelector
1732 inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
1733 : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
1735 virtual QStringList nodeNames(NodePtr node) const;
1736 virtual QString attribute(NodePtr node, const QString &name) const;
1737 virtual bool hasAttributes(NodePtr node) const;
1738 virtual bool isNullNode(NodePtr node) const;
1739 virtual NodePtr parentNode(NodePtr node) const;
1740 virtual NodePtr previousSiblingNode(NodePtr node) const;
1741 virtual NodePtr duplicateNode(NodePtr node) const;
1742 virtual void freeNode(NodePtr node) const;
1745 const QTextHtmlParser *parser;
1748 QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
1750 return QStringList(parser->at(node.id).tag.toLower());
1753 #endif // QT_NO_CSSPARSER
1755 static inline int findAttribute(const QStringList &attributes, const QString &name)
1759 idx = attributes.indexOf(name, idx + 1);
1760 } while (idx != -1 && (idx % 2 == 1));
1764 #ifndef QT_NO_CSSPARSER
1766 QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
1768 const QStringList &attributes = parser->at(node.id).attributes;
1769 const int idx = findAttribute(attributes, name);
1772 return attributes.at(idx + 1);
1775 bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
1777 const QStringList &attributes = parser->at(node.id).attributes;
1778 return !attributes.isEmpty();
1781 bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
1783 return node.id == 0;
1786 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
1791 parent.id = parser->at(node.id).parent;
1796 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
1801 QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
1807 int parent = parser->at(node.id).parent;
1810 const int childIdx = parser->at(parent).children.indexOf(node.id);
1813 sibling.id = parser->at(parent).children.at(childIdx - 1);
1817 void QTextHtmlStyleSelector::freeNode(NodePtr) const
1821 void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
1823 for (int i = 0; i < sheet.importRules.count(); ++i) {
1824 const QCss::ImportRule &rule = sheet.importRules.at(i);
1825 if (rule.media.isEmpty()
1826 || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
1827 importStyleSheet(rule.href);
1831 void QTextHtmlParser::importStyleSheet(const QString &href)
1833 if (!resourceProvider)
1835 for (int i = 0; i < externalStyleSheets.count(); ++i)
1836 if (externalStyleSheets.at(i).url == href)
1839 QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
1841 if (res.type() == QVariant::String) {
1842 css = res.toString();
1843 } else if (res.type() == QVariant::ByteArray) {
1844 // #### detect @charset
1845 css = QString::fromUtf8(res.toByteArray());
1847 if (!css.isEmpty()) {
1848 QCss::Parser parser(css);
1849 QCss::StyleSheet sheet;
1850 parser.parse(&sheet, Qt::CaseInsensitive);
1851 externalStyleSheets.append(ExternalStyleSheet(href, sheet));
1852 resolveStyleSheetImports(sheet);
1856 QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
1858 QVector<QCss::Declaration> decls;
1860 QTextHtmlStyleSelector selector(this);
1863 selector.styleSheets.resize((resourceProvider ? 1 : 0)
1864 + externalStyleSheets.count()
1865 + inlineStyleSheets.count());
1866 if (resourceProvider)
1867 selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
1869 for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
1870 selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
1872 for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
1873 selector.styleSheets[idx] = inlineStyleSheets.at(i);
1875 selector.medium = QLatin1String("screen");
1877 QCss::StyleSelector::NodePtr n;
1880 const char *extraPseudo = 0;
1881 if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
1882 extraPseudo = "link";
1883 decls = selector.declarationsForNode(n, extraPseudo);
1888 bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
1899 #endif // QT_NO_CSSPARSER
1901 #endif // QT_NO_TEXTHTMLPARSER