1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the text module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qtexthtmlparser_p.h"
31
#include <qbytearray.h>
32
#include <qtextcodec.h>
33
#include <qapplication.h>
37
#include "qtextdocument.h"
38
#include "qtextformat_p.h"
39
#include "qtextdocument_p.h"
40
#include "qtextcursor.h"
42
const int DefaultFontSize = 12;
44
#define MAX_ENTITY 259
45
static const struct QTextHtmlEntity { const char *name; quint16 code; } entities[MAX_ENTITY]= {
64
{ "Epsilon", 0x0395 },
85
{ "Omicron", 0x039f },
100
{ "Uacute", 0x00da },
102
{ "Ugrave", 0x00d9 },
103
{ "Upsilon", 0x03a5 },
106
{ "Yacute", 0x00dd },
109
{ "aacute", 0x00e1 },
113
{ "agrave", 0x00e0 },
114
{ "alefsym", 0x2135 },
122
{ "atilde", 0x00e3 },
126
{ "brvbar", 0x00a6 },
129
{ "ccedil", 0x00e7 },
139
{ "curren", 0x00a4 },
141
{ "dagger", 0x2020 },
146
{ "divide", 0x00f7 },
147
{ "eacute", 0x00e9 },
149
{ "egrave", 0x00e8 },
153
{ "epsilon", 0x03b5 },
161
{ "forall", 0x2200 },
162
{ "frac12", 0x00bd },
163
{ "frac14", 0x00bc },
164
{ "frac34", 0x00be },
171
{ "hearts", 0x2665 },
172
{ "hellip", 0x2026 },
173
{ "iacute", 0x00ed },
176
{ "igrave", 0x00ec },
181
{ "iquest", 0x00bf },
186
{ "lambda", 0x03bb },
193
{ "lfloor", 0x230a },
194
{ "lowast", 0x2217 },
197
{ "lsaquo", 0x2039 },
203
{ "middot", 0x00b7 },
214
{ "ntilde", 0x00f1 },
216
{ "oacute", 0x00f3 },
219
{ "ograve", 0x00f2 },
222
{ "omicron", 0x03bf },
227
{ "oslash", 0x00f8 },
228
{ "otilde", 0x00f5 },
229
{ "otimes", 0x2297 },
233
{ "percnt", 0x0025 },
234
{ "permil", 0x2030 },
239
{ "plusmn", 0x00b1 },
255
{ "rfloor", 0x230b },
258
{ "rsaquo", 0x203a },
261
{ "scaron", 0x0161 },
266
{ "sigmaf", 0x03c2 },
268
{ "spades", 0x2660 },
279
{ "there4", 0x2234 },
281
{ "thetasym", 0x03d1 },
282
{ "thinsp", 0x2009 },
288
{ "uacute", 0x00fa },
291
{ "ugrave", 0x00f9 },
294
{ "upsilon", 0x03c5 },
296
{ "weierp", 0x2118 },
298
{ "yacute", 0x00fd },
307
static bool operator<(const QString &entityStr, const QTextHtmlEntity &entity)
309
return entityStr < QLatin1String(entity.name);
312
static bool operator<(const QTextHtmlEntity &entity, const QString &entityStr)
314
return QLatin1String(entity.name) < entityStr;
317
static QChar resolveEntity(const QString &entity)
319
const QTextHtmlEntity *start = &entities[0];
320
const QTextHtmlEntity *end = &entities[MAX_ENTITY-1];
321
const QTextHtmlEntity *e = qBinaryFind(start, end, entity);
327
// the displayMode value is according to the what are blocks in the piecetable, not
328
// what the w3c defines.
329
static const QTextHtmlElement elements[Html_NumElements+1]= {
330
{ "a", Html_a, QTextHtmlElement::DisplayInline },
331
{ "b", Html_b, QTextHtmlElement::DisplayInline },
332
{ "big", Html_big, QTextHtmlElement::DisplayInline },
333
{ "blockquote", Html_blockquote, QTextHtmlElement::DisplayBlock },
334
{ "body", Html_body, QTextHtmlElement::DisplayBlock },
335
{ "br", Html_br, QTextHtmlElement::DisplayInline },
336
{ "center", Html_center, QTextHtmlElement::DisplayBlock },
337
{ "code", Html_code, QTextHtmlElement::DisplayInline },
338
{ "dd", Html_dd, QTextHtmlElement::DisplayBlock },
339
{ "div", Html_div, QTextHtmlElement::DisplayBlock },
340
{ "dl", Html_dl, QTextHtmlElement::DisplayBlock },
341
{ "dt", Html_dt, QTextHtmlElement::DisplayBlock },
342
{ "em", Html_em, QTextHtmlElement::DisplayInline },
343
{ "font", Html_font, QTextHtmlElement::DisplayInline },
344
{ "h1", Html_h1, QTextHtmlElement::DisplayBlock },
345
{ "h2", Html_h2, QTextHtmlElement::DisplayBlock },
346
{ "h3", Html_h3, QTextHtmlElement::DisplayBlock },
347
{ "h4", Html_h4, QTextHtmlElement::DisplayBlock },
348
{ "h5", Html_h5, QTextHtmlElement::DisplayBlock },
349
{ "h6", Html_h6, QTextHtmlElement::DisplayBlock },
350
{ "head", Html_head, QTextHtmlElement::DisplayNone },
351
{ "hr", Html_hr, QTextHtmlElement::DisplayInline },
352
{ "html", Html_html, QTextHtmlElement::DisplayInline },
353
{ "i", Html_i, QTextHtmlElement::DisplayInline },
354
{ "img", Html_img, QTextHtmlElement::DisplayInline },
355
{ "li", Html_li, QTextHtmlElement::DisplayBlock },
356
{ "meta", Html_meta, QTextHtmlElement::DisplayNone },
357
{ "nobr", Html_nobr, QTextHtmlElement::DisplayInline },
358
{ "ol", Html_ol, QTextHtmlElement::DisplayBlock },
359
{ "p", Html_p, QTextHtmlElement::DisplayBlock },
360
{ "pre", Html_pre, QTextHtmlElement::DisplayBlock },
361
{ "qt", Html_qt, QTextHtmlElement::DisplayBlock },
362
{ "s", Html_s, QTextHtmlElement::DisplayInline },
363
{ "small", Html_small, QTextHtmlElement::DisplayInline },
364
{ "span", Html_span, QTextHtmlElement::DisplayInline },
365
{ "strong", Html_strong, QTextHtmlElement::DisplayInline },
366
{ "style", Html_style, QTextHtmlElement::DisplayNone },
367
{ "sub", Html_sub, QTextHtmlElement::DisplayInline },
368
{ "sup", Html_sup, QTextHtmlElement::DisplayInline },
369
{ "table", Html_table, QTextHtmlElement::DisplayBlock },
370
{ "td", Html_td, QTextHtmlElement::DisplayBlock },
371
{ "th", Html_th, QTextHtmlElement::DisplayBlock },
372
{ "title", Html_title, QTextHtmlElement::DisplayNone },
373
{ "tr", Html_tr, QTextHtmlElement::DisplayBlock },
374
{ "tt", Html_tt, QTextHtmlElement::DisplayInline },
375
{ "u", Html_u, QTextHtmlElement::DisplayInline },
376
{ "ul", Html_ul, QTextHtmlElement::DisplayBlock },
377
{ 0, 0, QTextHtmlElement::DisplayNone }
381
static bool operator<(const QString &str, const QTextHtmlElement &e)
383
return str < QLatin1String(e.name);
386
static bool operator<(const QTextHtmlElement &e, const QString &str)
388
return QLatin1String(e.name) < str;
391
static const QTextHtmlElement *lookupElement(const QString &element)
393
const QTextHtmlElement *start = &elements[0];
394
const QTextHtmlElement *end = &elements[Html_NumElements];
395
const QTextHtmlElement *e = qBinaryFind(start, end, element);
396
Q_ASSERT(!e->name || e->name == element);
400
int QTextHtmlParser::lookupElement(const QString &element)
402
const QTextHtmlElement *e = ::lookupElement(element);
408
static int scaleFontPointSize(int fontPointSize, int logicalFontSize, int logicalFontSizeStep = 0)
410
if (logicalFontSize != -1 || logicalFontSizeStep) {
411
int logical = logicalFontSize;
414
logical += logicalFontSizeStep;
417
else if (logical > 7)
421
fontPointSize = (7 * fontPointSize) / 10;
424
fontPointSize = (8 * fontPointSize) / 10;
427
fontPointSize = (12 * fontPointSize) / 10;
430
fontPointSize = (15 * fontPointSize) / 10;
433
fontPointSize = 2 * fontPointSize;
436
fontPointSize = (24 * fontPointSize) / 10;
440
return fontPointSize;
443
// quotes newlines as "\\n"
444
static QString quoteNewline(const QString &s)
447
n.replace('\n', "\\n");
451
QTextHtmlParserNode::QTextHtmlParserNode()
452
: parent(0), id(-1), isBlock(false), isListItem(false), isListStart(false), isTableCell(false), isAnchor(false),
453
fontItalic(false), fontUnderline(false), fontOverline(false), fontStrikeOut(false), fontFixedPitch(false),
454
cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false), hasFontPointSize(false),
455
hasCssBlockIndent(false), hasCssListIndent(false), isEmptyParagraph(false), direction(3),
456
displayMode(QTextHtmlElement::DisplayInline), fontPointSize(DefaultFontSize),
457
fontWeight(QFont::Normal), alignment(0), verticalAlignment(QTextCharFormat::AlignNormal),
458
listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
459
tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0), cssBlockIndent(0),
460
cssListIndent(0), text_indent(0), wsm(WhiteSpaceModeUndefined)
462
margin[QTextHtmlParser::MarginLeft] = 0;
463
margin[QTextHtmlParser::MarginRight] = 0;
464
margin[QTextHtmlParser::MarginTop] = 0;
465
margin[QTextHtmlParser::MarginBottom] = 0;
468
QTextCharFormat QTextHtmlParserNode::charFormat() const
470
QTextCharFormat format;
472
format.setFontItalic(fontItalic);
473
format.setFontUnderline(fontUnderline);
474
format.setFontOverline(fontOverline);
475
format.setFontStrikeOut(fontStrikeOut);
476
format.setFontFixedPitch(fontFixedPitch);
477
if (fontFamily.size())
478
format.setFontFamily(fontFamily);
479
if (hasFontPointSize)
480
format.setFontPointSize(fontPointSize);
481
format.setFontWeight(fontWeight);
483
format.setForeground(QBrush(color));
484
if (verticalAlignment != QTextCharFormat::AlignNormal)
485
format.setVerticalAlignment(verticalAlignment);
487
format.setAnchor(true);
488
format.setAnchorHref(anchorHref);
489
format.setAnchorName(anchorName);
495
QTextBlockFormat QTextHtmlParserNode::blockFormat() const
497
QTextBlockFormat format;
500
format.setAlignment(alignment);
502
format.setLayoutDirection(Qt::LayoutDirection(direction));
504
if (hasCssBlockIndent)
505
format.setIndent(cssBlockIndent);
506
if (text_indent != 0.)
507
format.setTextIndent(text_indent);
512
void QTextHtmlParser::dumpHtml()
514
for (int i = 0; i < count(); ++i) {
515
qDebug().nospace() << qPrintable(QString(depth(i)*4, ' '))
516
<< qPrintable(at(i).tag) << ":"
517
<< quoteNewline(at(i).text);
522
QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
524
QTextHtmlParserNode *lastNode = &nodes.last();
525
QTextHtmlParserNode *newNode = 0;
527
bool reuseLastNode = true;
529
if (nodes.count() == 1) {
530
reuseLastNode = false;
531
} else if (lastNode->tag.isEmpty()) {
533
if (lastNode->text.isEmpty()) {
534
reuseLastNode = true;
535
} else { // last node is a text node (empty tag) with some text
537
if (lastNode->text == QLatin1String(" ")) {
539
// re-use last node if whitspace'ed, unless it's part of a
540
// <span>Foo</span> <span>Bar</span> alike sequence
541
const QTextHtmlParserNode *secondLastNode = &at(count() - 2);
542
if (secondLastNode->displayMode == QTextHtmlElement::DisplayInline
543
&& secondLastNode->parent == lastNode->parent) {
544
reuseLastNode = false;
546
reuseLastNode = true;
549
// text node with real (non-whitespace) text -> nothing to re-use
550
reuseLastNode = false;
556
// last node had a proper tag -> nothing to re-use
557
reuseLastNode = false;
562
newNode->tag.clear();
563
newNode->text.clear();
566
nodes.resize(nodes.size() + 1);
567
newNode = &nodes.last();
570
newNode->parent = parent;
574
void QTextHtmlParser::parse(const QString &text)
581
textEditMode = false;
586
int QTextHtmlParser::depth(int i) const
596
int QTextHtmlParser::margin(int i, int mar) const {
598
const QTextHtmlParserNode *node;
599
if (mar == MarginLeft
600
|| mar == MarginRight) {
605
m += node->margin[mar];
612
int QTextHtmlParser::topMargin(int i) const
615
const QTextHtmlParserNode *node;
620
m = qMax(m, node->margin[MarginTop]);
622
// collapsing margins across table cells makes no sense
623
if (node->isTableCell)
626
// don't collapse margins across list items
627
// (the top margin of the list is merged as part of the block
628
// merging in documentfragment.cpp)
629
if (node->isListItem)
634
// <ul> <-- this one should not take the first <ul>'s margin into account
635
if (node->isNestedList(this))
638
// get previous block
639
while (i-1 && !at(i-1).isBlock)
641
if (i && node->parent == at(i).parent)
648
int QTextHtmlParser::bottomMargin(int i) const
651
const QTextHtmlParserNode *node;
656
m = qMax(m, node->margin[MarginBottom]);
658
// collapsing margins across table cells makes no sense
659
if (node->isTableCell)
662
// don't collapse margins across list items
663
if (node->isListItem)
668
// <ul> <-- this one should not take the first <ul>'s margin into account
669
if (node->isNestedList(this))
673
while (i+1 < count() && !at(i+1).isBlock)
675
if (i && node->parent == at(i).parent)
682
void QTextHtmlParser::eatSpace()
684
while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
688
void QTextHtmlParser::parse() {
689
QTextHtmlParserNode::WhiteSpaceMode wsm = QTextHtmlParserNode::WhiteSpaceNormal;
691
QChar c = txt.at(pos++);
692
if (c == QLatin1Char('<')) {
694
wsm = nodes.last().wsm;
695
} else if (c == QLatin1Char('&')) {
696
nodes.last().text += parseEntity();
698
if (c.isSpace() && c != QChar(QChar::Nbsp) && c != QChar::ParagraphSeparator) {
699
if (wsm == QTextHtmlParserNode::WhiteSpacePre
701
if (c == QLatin1Char('\n'))
702
c = QChar::LineSeparator;
703
else if (c == QLatin1Char('\r'))
707
&& c == QChar::LineSeparator)
709
} else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) { // non-pre mode: collapse whitespace except nbsp
710
while (pos < len && txt.at(pos).isSpace()
711
&& txt.at(pos) != QChar::Nbsp)
713
if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
716
c = QLatin1Char(' ');
719
nodes.last().text += c;
724
// parses a tag after "<"
725
void QTextHtmlParser::parseTag()
729
// handle comments and other exclamation mark declarations
730
if (hasPrefix(QLatin1Char('!'))) {
731
parseExclamationTag();
736
// if close tag just close
737
if (hasPrefix(QLatin1Char('/'))) {
743
while (p && at(p).tag.size() == 0)
746
QTextHtmlParserNode *node = newNode(p);
749
node->tag = parseWord().toLower();
751
const QTextHtmlElement *elem = ::lookupElement(node->tag);
754
node->isBlock = (elem->displayMode == QTextHtmlElement::DisplayBlock);
755
node->displayMode = elem->displayMode;
760
node->isListItem = (node->id == Html_li);
761
node->isListStart = (node->id == Html_ol || node->id == Html_ul);
762
node->isTableCell = (node->id == Html_td || node->id == Html_th);
766
// _need_ at least one space after the tag name, otherwise there can't be attributes
767
if (pos < len && txt.at(pos).isSpace())
770
// special handling for anchors with href attribute (hyperlinks)
771
if (node->isAnchor && !node->anchorHref.isEmpty()) {
772
node->fontUnderline = true; // ####
773
node->color = Qt::blue; // ####
777
bool tagClosed = false;
778
while (pos < len && txt.at(pos) != QLatin1Char('>')) {
779
if (txt.at(pos) == QLatin1Char('/'))
787
if (node->wsm != QTextHtmlParserNode::WhiteSpacePre
788
&& node->wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
791
if (node->mayNotHaveChildren() || tagClosed) {
792
newNode(node->parent);
797
// parses a tag beginning with "/"
798
void QTextHtmlParser::parseCloseTag()
801
QString tag = parseWord().toLower().trimmed();
803
QChar c = txt.at(pos++);
804
if (c == QLatin1Char('>'))
808
// find corresponding open node
811
&& at(p - 1).tag == tag
812
&& at(p - 1).mayNotHaveChildren())
815
while (p && at(p).tag != tag)
818
newNode(at(p).parent);
822
// parses a tag beginning with "!"
823
void QTextHtmlParser::parseExclamationTag()
826
if (hasPrefix(QLatin1Char('-'),1) && hasPrefix(QLatin1Char('-'),2)) {
829
int end = txt.indexOf(QLatin1String("-->"), pos);
830
pos = (end >= 0 ? end + 3 : len);
834
QChar c = txt.at(pos++);
835
if (c == QLatin1Char('>'))
841
// parses an entity after "&", and returns it
842
QString QTextHtmlParser::parseEntity()
847
QChar c = txt.at(pos++);
848
if (c.isSpace() || pos - recover > 8) {
851
if (c == QLatin1Char(';'))
856
QChar resolved = resolveEntity(entity);
857
if (!resolved.isNull())
858
return QString(resolved);
860
if (entity.length() > 1 && entity.at(0) == QLatin1Char('#')) {
861
entity.remove(0, 1); // removing leading #
866
if (entity.at(0).toLower() == QLatin1Char('x')) { // hex entity?
871
int uc = entity.toInt(&ok, base);
873
if (uc == 151) // ### hack for designer manual
879
ushort high = uc/0x400 + 0xd800;
880
ushort low = uc%0x400 + 0xdc00;
881
str.append(QChar(high));
882
str.append(QChar(low));
884
str.append(QChar(uc));
891
return QLatin1String("&");
894
// parses one word, possibly quoted, and returns it
895
QString QTextHtmlParser::parseWord()
898
if (hasPrefix(QLatin1Char('\"'))) { // double quotes
901
QChar c = txt.at(pos++);
902
if (c == QLatin1Char('\"'))
904
else if (c == QLatin1Char('&'))
905
word += parseEntity();
909
} else if (hasPrefix(QLatin1Char('\''))) { // single quotes
912
QChar c = txt.at(pos++);
913
if (c == QLatin1Char('\''))
918
} else { // normal text
920
QChar c = txt.at(pos++);
921
if (c == QLatin1Char('>')
922
|| (c == '/' && hasPrefix(QLatin1Char('>'), 1))
923
|| c == QLatin1Char('<')
924
|| c == QLatin1Char('=')
929
if (c == QLatin1Char('&'))
930
word += parseEntity();
938
// gives the new node the right parent
939
void QTextHtmlParser::resolveParent()
941
QTextHtmlParserNode *node = &nodes.last();
942
int p = node->parent;
944
// block elements close inline elements
945
// ... with the exception of the font element ... grmbl ...
949
&& at(p).id != Html_font) {
953
// some elements are not self nesting
954
if (node->tag == at(p).tag) {
955
if (node->isNotSelfNesting())
959
// some elements are not allowed in certain contexts
960
while (p && !node->allowedInContext(at(p).id)
961
// ### make new styles aware of empty tags
962
|| at(p).id == Html_hr
963
|| at(p).id == Html_br
964
|| at(p).id == Html_img
971
// makes it easier to traverse the tree, later
972
nodes[p].children.append(nodes.count() - 1);
975
// sets all properties on the new node
976
void QTextHtmlParser::resolveNode()
978
QTextHtmlParserNode *node = &nodes.last();
979
const QTextHtmlParserNode *parent = &nodes.at(node->parent);
980
node->initializeProperties(parent, this);
983
bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
990
if (parser->at(p).isListStart)
992
p = parser->at(p).parent;
997
void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
999
// inherit properties from parent element
1000
isAnchor = parent->isAnchor;
1001
fontItalic = parent->fontItalic;
1002
fontUnderline = parent->fontUnderline;
1003
fontOverline = parent->fontOverline;
1004
fontStrikeOut = parent->fontStrikeOut;
1005
fontFixedPitch = parent->fontFixedPitch;
1006
fontFamily = parent->fontFamily;
1007
hasFontPointSize = parent->hasFontPointSize;
1008
fontPointSize = parent->fontPointSize;
1009
fontWeight = parent->fontWeight;
1010
color = parent->color;
1011
verticalAlignment = parent->verticalAlignment;
1013
if (parent->id != Html_table) {
1014
alignment = parent->alignment;
1016
// we don't paint per-row background colors, yet. so as an
1017
// exception inherit the background color here
1018
if (parent->id == Html_tr && isTableCell) {
1019
bgColor = parent->bgColor;
1022
listStyle = parent->listStyle;
1023
anchorHref = parent->anchorHref;
1024
// makes no sense to inherit that property, a named anchor is a single point
1025
// in the document, which is set by the DocumentFragment
1026
//anchorName = parent->anchorName;
1029
// initialize remaining properties
1030
margin[QTextHtmlParser::MarginLeft] = 0;
1031
margin[QTextHtmlParser::MarginRight] = 0;
1032
margin[QTextHtmlParser::MarginTop] = 0;
1033
margin[QTextHtmlParser::MarginBottom] = 0;
1034
cssFloat = QTextFrameFormat::InFlow;
1036
const int oldFontPointSize = fontPointSize;
1038
// set element specific attributes
1048
fontPointSize = scaleFontPointSize(fontPointSize, -1 /*logical*/, 1 /*step*/);
1051
fontPointSize = scaleFontPointSize(fontPointSize, -1 /*logical*/, -1 /*step*/);
1055
fontWeight = QFont::Bold;
1058
fontWeight = QFont::Bold;
1059
fontPointSize = scaleFontPointSize(DefaultFontSize, 6);
1060
margin[QTextHtmlParser::MarginTop] = 18;
1061
margin[QTextHtmlParser::MarginBottom] = 12;
1064
fontWeight = QFont::Bold;
1065
fontPointSize = scaleFontPointSize(DefaultFontSize, 5);
1066
margin[QTextHtmlParser::MarginTop] = 16;
1067
margin[QTextHtmlParser::MarginBottom] = 12;
1070
fontWeight = QFont::Bold;
1071
fontPointSize = scaleFontPointSize(DefaultFontSize, 4);
1072
margin[QTextHtmlParser::MarginTop] = 14;
1073
margin[QTextHtmlParser::MarginBottom] = 12;
1076
fontWeight = QFont::Bold;
1077
fontPointSize = scaleFontPointSize(DefaultFontSize, 3);
1078
margin[QTextHtmlParser::MarginTop] = 12;
1079
margin[QTextHtmlParser::MarginBottom] = 12;
1082
fontWeight = QFont::Bold;
1083
fontPointSize = scaleFontPointSize(DefaultFontSize, 2);
1084
margin[QTextHtmlParser::MarginTop] = 12;
1085
margin[QTextHtmlParser::MarginBottom] = 4;
1088
margin[QTextHtmlParser::MarginTop] = 12;
1089
margin[QTextHtmlParser::MarginBottom] = 12;
1092
alignment = Qt::AlignCenter;
1095
listStyle = QTextListFormat::ListDisc;
1096
// nested lists don't have margins, except for the toplevel one
1097
if (!isNestedList(parser)) {
1098
margin[QTextHtmlParser::MarginTop] = 12;
1099
margin[QTextHtmlParser::MarginBottom] = 12;
1101
// no left margin as we use indenting instead
1104
listStyle = QTextListFormat::ListDecimal;
1105
// nested lists don't have margins, except for the toplevel one
1106
if (!isNestedList(parser)) {
1107
margin[QTextHtmlParser::MarginTop] = 12;
1108
margin[QTextHtmlParser::MarginBottom] = 12;
1110
// no left margin as we use indenting instead
1114
fontFamily = QString::fromLatin1("Courier New,courier");
1115
// <tt> uses a fixed font, so set the property
1116
fontFixedPitch = true;
1119
text = QChar(QChar::LineSeparator);
1123
fontFamily = QString::fromLatin1("Courier New,courier");
1124
wsm = WhiteSpacePre;
1125
margin[QTextHtmlParser::MarginTop] = 12;
1126
margin[QTextHtmlParser::MarginBottom] = 12;
1127
// <pre> uses a fixed font
1128
fontFixedPitch = true;
1130
case Html_blockquote:
1131
margin[QTextHtmlParser::MarginLeft] = 40;
1132
margin[QTextHtmlParser::MarginRight] = 40;
1135
margin[QTextHtmlParser::MarginTop] = 8;
1136
margin[QTextHtmlParser::MarginBottom] = 8;
1139
margin[QTextHtmlParser::MarginLeft] = 30;
1142
fontUnderline = true;
1145
fontStrikeOut = true;
1148
wsm = WhiteSpaceNoWrap;
1151
fontWeight = QFont::Bold;
1152
alignment = Qt::AlignCenter;
1155
alignment = Qt::AlignLeft;
1158
verticalAlignment = QTextCharFormat::AlignSubScript;
1161
verticalAlignment = QTextCharFormat::AlignSuperScript;
1166
if (fontPointSize != oldFontPointSize)
1167
hasFontPointSize = true;
1170
static bool setIntAttribute(int *destination, const QString &value)
1173
int val = value.toInt(&ok);
1180
static void setWidthAttribute(QTextLength *width, QString value)
1184
intVal = value.toInt(&ok);
1186
*width = QTextLength(QTextLength::FixedLength, intVal);
1188
value = value.trimmed();
1189
if (!value.isEmpty() && value.at(value.length() - 1) == QLatin1Char('%')) {
1191
intVal = value.toInt(&ok);
1193
*width = QTextLength(QTextLength::PercentageLength, intVal);
1198
static QTextHtmlParserNode::WhiteSpaceMode stringToWhiteSpaceMode(const QString &s)
1200
if (s == QLatin1String("normal"))
1201
return QTextHtmlParserNode::WhiteSpaceNormal;
1202
else if (s == QLatin1String("pre"))
1203
return QTextHtmlParserNode::WhiteSpacePre;
1204
else if (s == QLatin1String("nowrap"))
1205
return QTextHtmlParserNode::WhiteSpaceNoWrap;
1206
else if (s == QLatin1String("pre-wrap"))
1207
return QTextHtmlParserNode::WhiteSpacePreWrap;
1209
return QTextHtmlParserNode::WhiteSpaceModeUndefined;
1212
void QTextHtmlParser::parseAttributes()
1214
// local state variable for qt3 textedit mode
1215
bool seenQt3Richtext = false;
1217
QTextHtmlParserNode *node = &nodes.last();
1220
if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
1222
QString key = parseWord().toLower();
1223
QString value = QLatin1String("1");
1224
if (key.size() == 0)
1227
if (hasPrefix(QLatin1Char('='))){
1230
value = parseWord();
1232
if (value.size() == 0)
1234
if (node->id == Html_font) {
1235
// the infamous font tag
1236
if (key == QLatin1String("size") && value.size()) {
1237
int n = value.toInt();
1238
if (value.at(0) == QLatin1Char('+') || value.at(0) == QLatin1Char('-'))
1240
node->fontPointSize = scaleFontPointSize(DefaultFontSize, n);
1241
node->hasFontPointSize = true;
1242
} else if (key == QLatin1String("face")) {
1243
node->fontFamily = value;
1244
} else if (key == QLatin1String("color")) {
1245
node->color.setNamedColor(value);
1247
} else if (node->id == Html_ol
1248
|| node->id == Html_ul) {
1249
if (key == QLatin1String("type")) {
1250
node->hasOwnListStyle = true;
1251
if (value == QLatin1String("1")) {
1252
node->listStyle = QTextListFormat::ListDecimal;
1253
} else if (value == QLatin1String("a")) {
1254
node->listStyle = QTextListFormat::ListLowerAlpha;
1255
} else if (value == QLatin1String("A")) {
1256
node->listStyle = QTextListFormat::ListUpperAlpha;
1258
value = value.toLower();
1259
if (value == QLatin1String("square"))
1260
node->listStyle = QTextListFormat::ListSquare;
1261
else if (value == QLatin1String("disc"))
1262
node->listStyle = QTextListFormat::ListDisc;
1263
else if (value == QLatin1String("circle"))
1264
node->listStyle = QTextListFormat::ListCircle;
1267
} else if (node->isAnchor) {
1268
if (key == QLatin1String("href"))
1269
node->anchorHref = value;
1270
else if (key == QLatin1String("name"))
1271
node->anchorName = value;
1272
} else if (node->id == Html_img) {
1273
if (key == QLatin1String("src") || key == QLatin1String("source")) {
1274
node->imageName = value;
1275
} else if (key == QLatin1String("width")) {
1276
setIntAttribute(&node->imageWidth, value);
1277
} else if (key == QLatin1String("height")) {
1278
setIntAttribute(&node->imageHeight, value);
1280
} else if (node->id == Html_tr || node->id == Html_body) {
1281
if (key == QLatin1String("bgcolor"))
1282
node->bgColor.setNamedColor(value);
1283
} else if (node->isTableCell) {
1284
if (key == QLatin1String("width")) {
1285
setWidthAttribute(&node->width, value);
1286
} else if (key == QLatin1String("bgcolor")) {
1287
node->bgColor.setNamedColor(value);
1288
} else if (key == QLatin1String("rowspan")) {
1289
setIntAttribute(&node->tableCellRowSpan, value);
1290
} else if (key == QLatin1String("colspan")) {
1291
setIntAttribute(&node->tableCellColSpan, value);
1293
} else if (node->id == Html_table) {
1294
if (key == QLatin1String("border")) {
1295
setIntAttribute(&node->tableBorder, value);
1296
} else if (key == QLatin1String("bgcolor")) {
1297
node->bgColor.setNamedColor(value);
1298
} else if (key == QLatin1String("cellspacing")) {
1299
setIntAttribute(&node->tableCellSpacing, value);
1300
} else if (key == QLatin1String("cellpadding")) {
1301
setIntAttribute(&node->tableCellPadding, value);
1302
} else if (key == QLatin1String("width")) {
1303
setWidthAttribute(&node->width, value);
1305
} else if (node->id == Html_meta) {
1306
if (key == QLatin1String("name")
1307
&& value == QLatin1String("qrichtext")) {
1308
seenQt3Richtext = true;
1311
if (key == QLatin1String("content")
1312
&& value == QLatin1String("1")
1313
&& seenQt3Richtext) {
1315
textEditMode = true;
1319
if (key == QLatin1String("style")) {
1320
// style parser taken from Qt 3
1322
int count = a.count(';')+1;
1323
for (int s = 0; s < count; s++) {
1324
QString style = a.section(';', s, s).trimmed();
1325
if (style.startsWith(QLatin1String("font-size:")) && style.endsWith(QLatin1String("pt"))) {
1326
node->fontPointSize = int(style.mid(10, style.length() - 12).trimmed().toDouble());
1327
node->hasFontPointSize = true;
1328
} if (style.startsWith(QLatin1String("font-style:"))) {
1329
QString s = style.mid(11).trimmed();
1330
if (s == QLatin1String("normal"))
1331
node->fontItalic = false;
1332
else if (s == QLatin1String("italic") || s == QLatin1String("oblique"))
1333
node->fontItalic = true;
1334
} else if (style.startsWith(QLatin1String("font-weight:"))) {
1335
QString s = style.mid(12);
1337
int n = s.toInt(&ok);
1339
node->fontWeight = n/8;
1340
} else if (style.startsWith(QLatin1String("font-family:"))) {
1341
node->fontFamily = style.mid(12).trimmed();
1342
} else if (style.startsWith(QLatin1String("text-decoration:"))) {
1343
QString s = style.mid(16);
1344
node->fontUnderline = static_cast<bool>(s.contains("underline"));
1345
node->fontOverline = static_cast<bool>(s.contains("overline"));
1346
node->fontStrikeOut = static_cast<bool>(s.contains("line-through"));
1348
} else if (style.startsWith(QLatin1String("vertical-align:"))) {
1349
QString s = style.mid(15).trimmed();
1350
if (s == QLatin1String("sub"))
1351
format.setVAlign(QTextFormat::AlignSubScript);
1352
else if (s == QLatin1String("super"))
1353
format.setVAlign(QTextFormat::AlignSuperScript);
1355
format.setVAlign(QTextFormat::AlignNormal);
1357
} else if (style.startsWith(QLatin1String("color:"))) {
1358
QString s = style.mid(6).trimmed();
1359
if (s.startsWith(QLatin1String("rgb("))
1360
&& s.at(s.length() - 1) == QLatin1Char(')')) {
1365
const QStringList rgb = s.split(',');
1366
if (rgb.count() == 3)
1367
node->color.setRgb(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt());
1369
node->color = QColor();
1371
node->color.setNamedColor(style.mid(6));
1373
} else if (style.startsWith(QLatin1String("float:"))) {
1374
QString s = style.mid(6).trimmed();
1375
node->cssFloat = QTextFrameFormat::InFlow;
1376
if (s == QLatin1String("left"))
1377
node->cssFloat = QTextFrameFormat::FloatLeft;
1378
else if (s == QLatin1String("right"))
1379
node->cssFloat = QTextFrameFormat::FloatRight;
1380
} else if (style.startsWith(QLatin1String("-qt-block-indent:"))) {
1381
const QString s = style.mid(17).trimmed();
1382
if (setIntAttribute(&node->cssBlockIndent, s))
1383
node->hasCssBlockIndent = true;
1384
} else if (style.startsWith(QLatin1String("text-indent:")) && style.endsWith(QLatin1String("px"))) {
1385
node->text_indent = style.mid(12, style.length() - 14).trimmed().toDouble();
1386
} else if (style.startsWith(QLatin1String("-qt-list-indent:"))) {
1387
const QString s = style.mid(16).trimmed();
1388
if (setIntAttribute(&node->cssListIndent, s)) {
1389
node->hasCssListIndent = true;
1391
} else if (style.startsWith(QLatin1String("-qt-paragraph-type:"))) {
1392
const QString s = style.mid(19).trimmed().toLower();
1393
if (s == QLatin1String("empty"))
1394
node->isEmptyParagraph = true;
1395
} else if (style.startsWith(QLatin1String("white-space:"))) {
1396
const QString s = style.mid(12).trimmed().toLower();
1397
QTextHtmlParserNode::WhiteSpaceMode ws = stringToWhiteSpaceMode(s);
1398
if (ws != QTextHtmlParserNode::WhiteSpaceModeUndefined)
1400
} else if (style.startsWith(QLatin1String("margin-top:")) && style.endsWith("px")) {
1401
const QString s = style.mid(11, style.length() - 13).trimmed();
1402
setIntAttribute(&node->margin[MarginTop], s);
1403
} else if (style.startsWith(QLatin1String("margin-bottom:")) && style.endsWith("px")) {
1404
const QString s = style.mid(14, style.length() - 16).trimmed();
1405
setIntAttribute(&node->margin[MarginBottom], s);
1406
} else if (style.startsWith(QLatin1String("margin-left:")) && style.endsWith("px")) {
1407
const QString s = style.mid(12, style.length() - 14).trimmed();
1408
setIntAttribute(&node->margin[MarginLeft], s);
1409
} else if (style.startsWith(QLatin1String("margin-right:")) && style.endsWith("px")) {
1410
const QString s = style.mid(13, style.length() - 15).trimmed();
1411
setIntAttribute(&node->margin[MarginRight], s);
1412
} else if (style.startsWith(QLatin1String("vertical-align:"))) {
1413
const QString s = style.mid(15, style.length() - 15).trimmed();
1415
node->verticalAlignment = QTextCharFormat::AlignSubScript;
1416
else if (s == "super")
1417
node->verticalAlignment = QTextCharFormat::AlignSuperScript;
1419
node->verticalAlignment = QTextCharFormat::AlignNormal;
1422
} else if (key == QLatin1String("align")) {
1423
if (value == QLatin1String("left"))
1424
node->alignment = Qt::AlignLeft|Qt::AlignAbsolute;
1425
else if (value == QLatin1String("right"))
1426
node->alignment = Qt::AlignRight|Qt::AlignAbsolute;
1427
else if (value == QLatin1String("center"))
1428
node->alignment = Qt::AlignHCenter;
1429
else if (value == QLatin1String("justify"))
1430
node->alignment = Qt::AlignJustify;
1433
if (node->id == Html_img) {
1434
if (node->alignment == Qt::AlignLeft)
1435
node->cssFloat = QTextFrameFormat::FloatLeft;
1436
else if (node->alignment == Qt::AlignRight)
1437
node->cssFloat = QTextFrameFormat::FloatRight;
1439
} else if (key == QLatin1String("dir")) {
1440
if (value == QLatin1String("ltr"))
1441
node->direction = Qt::LeftToRight;
1442
else if (value == QLatin1String("rtl"))
1443
node->direction = Qt::RightToLeft;