Compile with clang when C++11 support is enabled
[profile/ivi/qtbase.git] / src / gui / text / qtextdocumentfragment.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qtextdocumentfragment.h"
43 #include "qtextdocumentfragment_p.h"
44 #include "qtextcursor_p.h"
45 #include "qtextlist.h"
46
47 #include <qdebug.h>
48 #include <qtextcodec.h>
49 #include <qbytearray.h>
50 #include <qdatastream.h>
51 #include <qdatetime.h>
52
53 QT_BEGIN_NAMESPACE
54
55 QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
56 #if defined(Q_CC_DIAB) // compiler bug
57     : formatCollection(*_destination.d->priv->formatCollection()), originalText((const QString)_source.d->priv->buffer())
58 #else
59     : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
60 #endif
61 {
62     src = _source.d->priv;
63     dst = _destination.d->priv;
64     insertPos = _destination.position();
65     this->forceCharFormat = forceCharFormat;
66     primaryCharFormatIndex = convertFormatIndex(fmt);
67     cursor = _source;
68 }
69
70 int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
71 {
72     QTextFormat fmt = oldFormat;
73     if (objectIndexToSet != -1) {
74         fmt.setObjectIndex(objectIndexToSet);
75     } else if (fmt.objectIndex() != -1) {
76         int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
77         if (newObjectIndex == -1) {
78             QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex());
79             Q_ASSERT(objFormat.objectIndex() == -1);
80             newObjectIndex = formatCollection.createObjectIndex(objFormat);
81             objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
82         }
83         fmt.setObjectIndex(newObjectIndex);
84     }
85     int idx = formatCollection.indexForFormat(fmt);
86     Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type());
87     return idx;
88 }
89
90 int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex)
91 {
92     QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos);
93     const QTextFragmentData * const frag = fragIt.value();
94
95     Q_ASSERT(objectIndex == -1
96              || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1));
97
98     int charFormatIndex;
99     if (forceCharFormat)
100        charFormatIndex = primaryCharFormatIndex;
101     else
102        charFormatIndex = convertFormatIndex(frag->format, objectIndex);
103
104     const int inFragmentOffset = qMax(0, pos - fragIt.position());
105     int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos);
106
107     QTextBlock nextBlock = src->blocksFind(pos + 1);
108
109     int blockIdx = -2;
110     if (nextBlock.position() == pos + 1) {
111         blockIdx = convertFormatIndex(nextBlock.blockFormat());
112     } else if (pos == 0 && insertPos == 0) {
113         dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat());
114         dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat());
115     }
116
117     QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy);
118     if (txtToInsert.length() == 1
119         && (txtToInsert.at(0) == QChar::ParagraphSeparator
120             || txtToInsert.at(0) == QTextBeginningOfFrame
121             || txtToInsert.at(0) == QTextEndOfFrame
122            )
123        ) {
124         dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex);
125         ++insertPos;
126     } else {
127         if (nextBlock.textList()) {
128             QTextBlock dstBlock = dst->blocksFind(insertPos);
129             if (!dstBlock.textList()) {
130                 // insert a new text block with the block and char format from the
131                 // source block to make sure that the following text fragments
132                 // end up in a list as they should
133                 int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat());
134                 int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat());
135                 dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex);
136                 ++insertPos;
137             }
138         }
139         dst->insert(insertPos, txtToInsert, charFormatIndex);
140         const int userState = nextBlock.userState();
141         if (userState != -1)
142             dst->blocksFind(insertPos).setUserState(userState);
143         insertPos += txtToInsert.length();
144     }
145
146     return charsToCopy;
147 }
148
149 void QTextCopyHelper::appendFragments(int pos, int endPos)
150 {
151     Q_ASSERT(pos < endPos);
152
153     while (pos < endPos)
154         pos += appendFragment(pos, endPos);
155 }
156
157 void QTextCopyHelper::copy()
158 {
159     if (cursor.hasComplexSelection()) {
160         QTextTable *table = cursor.currentTable();
161         int row_start, col_start, num_rows, num_cols;
162         cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
163
164         QTextTableFormat tableFormat = table->format();
165         tableFormat.setColumns(num_cols);
166         tableFormat.clearColumnWidthConstraints();
167         const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat);
168
169         Q_ASSERT(row_start != -1);
170         for (int r = row_start; r < row_start + num_rows; ++r) {
171             for (int c = col_start; c < col_start + num_cols; ++c) {
172                 QTextTableCell cell = table->cellAt(r, c);
173                 const int rspan = cell.rowSpan();
174                 const int cspan = cell.columnSpan();
175                 if (rspan != 1) {
176                     int cr = cell.row();
177                     if (cr != r)
178                         continue;
179                 }
180                 if (cspan != 1) {
181                     int cc = cell.column();
182                     if (cc != c)
183                         continue;
184                 }
185
186                 // add the QTextBeginningOfFrame
187                 QTextCharFormat cellFormat = cell.format();
188                 if (r + rspan >= row_start + num_rows) {
189                     cellFormat.setTableCellRowSpan(row_start + num_rows - r);
190                 }
191                 if (c + cspan >= col_start + num_cols) {
192                     cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
193                 }
194                 const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex);
195
196                 int blockIdx = -2;
197                 const int cellPos = cell.firstPosition();
198                 QTextBlock block = src->blocksFind(cellPos);
199                 if (block.position() == cellPos) {
200                     blockIdx = convertFormatIndex(block.blockFormat());
201                 }
202
203                 dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex);
204                 ++insertPos;
205
206                 // nothing to add for empty cells
207                 if (cell.lastPosition() > cellPos) {
208                     // add the contents
209                     appendFragments(cellPos, cell.lastPosition());
210                 }
211             }
212         }
213
214         // add end of table
215         int end = table->lastPosition();
216         appendFragment(end, end+1, objectIndex);
217     } else {
218         appendFragments(cursor.selectionStart(), cursor.selectionEnd());
219     }
220 }
221
222 QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor)
223     : ref(1), doc(new QTextDocument), importedFromPlainText(false)
224 {
225     doc->setUndoRedoEnabled(false);
226
227     if (!_cursor.hasSelection())
228         return;
229
230     doc->docHandle()->beginEditBlock();
231     QTextCursor destCursor(doc);
232     QTextCopyHelper(_cursor, destCursor).copy();
233     doc->docHandle()->endEditBlock();
234
235     if (_cursor.d)
236         doc->docHandle()->mergeCachedResources(_cursor.d->priv);
237 }
238
239 void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const
240 {
241     if (_cursor.isNull())
242         return;
243
244     QTextDocumentPrivate *destPieceTable = _cursor.d->priv;
245     destPieceTable->beginEditBlock();
246
247     QTextCursor sourceCursor(doc);
248     sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
249     QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy();
250
251     destPieceTable->endEditBlock();
252 }
253
254 /*!
255     \class QTextDocumentFragment
256     \reentrant
257
258     \brief The QTextDocumentFragment class represents a piece of formatted text
259     from a QTextDocument.
260
261     \ingroup richtext-processing
262     \ingroup shared
263
264     A QTextDocumentFragment is a fragment of rich text, that can be inserted into
265     a QTextDocument. A document fragment can be created from a
266     QTextDocument, from a QTextCursor's selection, or from another
267     document fragment. Document fragments can also be created by the
268     static functions, fromPlainText() and fromHtml().
269
270     The contents of a document fragment can be obtained as plain text
271     by using the toPlainText() function, or it can be obtained as HTML
272     with toHtml().
273 */
274
275
276 /*!
277     Constructs an empty QTextDocumentFragment.
278
279     \sa isEmpty()
280 */
281 QTextDocumentFragment::QTextDocumentFragment()
282     : d(0)
283 {
284 }
285
286 /*!
287     Converts the given \a document into a QTextDocumentFragment.
288     Note that the QTextDocumentFragment only stores the document contents, not meta information
289     like the document's title.
290 */
291 QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
292     : d(0)
293 {
294     if (!document)
295         return;
296
297     QTextCursor cursor(const_cast<QTextDocument *>(document));
298     cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
299     d = new QTextDocumentFragmentPrivate(cursor);
300 }
301
302 /*!
303     Creates a QTextDocumentFragment from the \a{cursor}'s selection.
304     If the cursor doesn't have a selection, the created fragment is empty.
305
306     \sa isEmpty(), QTextCursor::selection()
307 */
308 QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
309     : d(0)
310 {
311     if (!cursor.hasSelection())
312         return;
313
314     d = new QTextDocumentFragmentPrivate(cursor);
315 }
316
317 /*!
318     \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
319
320     Copy constructor. Creates a copy of the \a other fragment.
321 */
322 QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
323     : d(rhs.d)
324 {
325     if (d)
326         d->ref.ref();
327 }
328
329 /*!
330     \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
331
332     Assigns the \a other fragment to this fragment.
333 */
334 QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
335 {
336     if (rhs.d)
337         rhs.d->ref.ref();
338     if (d && !d->ref.deref())
339         delete d;
340     d = rhs.d;
341     return *this;
342 }
343
344 /*!
345     Destroys the document fragment.
346 */
347 QTextDocumentFragment::~QTextDocumentFragment()
348 {
349     if (d && !d->ref.deref())
350         delete d;
351 }
352
353 /*!
354     Returns true if the fragment is empty; otherwise returns false.
355 */
356 bool QTextDocumentFragment::isEmpty() const
357 {
358     return !d || !d->doc || d->doc->docHandle()->length() <= 1;
359 }
360
361 /*!
362     Returns the document fragment's text as plain text (i.e. with no
363     formatting information).
364
365     \sa toHtml()
366 */
367 QString QTextDocumentFragment::toPlainText() const
368 {
369     if (!d)
370         return QString();
371
372     return d->doc->toPlainText();
373 }
374
375 #ifndef QT_NO_TEXTHTMLPARSER
376
377 /*!
378     \since 4.2
379
380     Returns the contents of the document fragment as HTML,
381     using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
382
383     \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
384 */
385 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
386 {
387     if (!d)
388         return QString();
389
390     return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
391 }
392
393 #endif // QT_NO_TEXTHTMLPARSER
394
395 /*!
396     Returns a document fragment that contains the given \a plainText.
397
398     When inserting such a fragment into a QTextDocument the current char format of
399     the QTextCursor used for insertion is used as format for the text.
400 */
401 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
402 {
403     QTextDocumentFragment res;
404
405     res.d = new QTextDocumentFragmentPrivate;
406     res.d->importedFromPlainText = true;
407     QTextCursor cursor(res.d->doc);
408     cursor.insertText(plainText);
409     return res;
410 }
411
412 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
413 {
414     if (style == QTextListFormat::ListDisc)
415         return QTextListFormat::ListCircle;
416     else if (style == QTextListFormat::ListCircle)
417         return QTextListFormat::ListSquare;
418     return style;
419 }
420
421 #ifndef QT_NO_TEXTHTMLPARSER
422
423 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
424     : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
425 {
426     cursor = QTextCursor(doc);
427     wsm = QTextHtmlParserNode::WhiteSpaceNormal;
428
429     QString html = _html;
430     const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
431     if (startFragmentPos != -1) {
432         QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
433
434         // Hack for Qt3
435         const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
436
437         const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
438         if (startFragmentPos < endFragmentPos)
439             html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
440         else
441             html = html.mid(startFragmentPos);
442
443         if (hasQtRichtextMetaTag)
444             html.prepend(qt3RichTextHeader);
445     }
446
447     parse(html, resourceProvider ? resourceProvider : doc);
448 //    dumpHtml();
449 }
450
451 void QTextHtmlImporter::import()
452 {
453     cursor.beginEditBlock();
454     hasBlock = true;
455     forceBlockMerging = false;
456     compressNextWhitespace = RemoveWhiteSpace;
457     blockTagClosed = false;
458     for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
459         currentNode = &at(currentNodeIdx);
460         wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
461
462         /*
463          * process each node in three stages:
464          * 1) check if the hierarchy changed and we therefore passed the
465          *    equivalent of a closing tag -> we may need to finish off
466          *    some structures like tables
467          *
468          * 2) check if the current node is a special node like a
469          *    <table>, <ul> or <img> tag that requires special processing
470          *
471          * 3) if the node should result in a QTextBlock create one and
472          *    finally insert text that may be attached to the node
473          */
474
475         /* emit 'closing' table blocks or adjust current indent level
476          * if we
477          *  1) are beyond the first node
478          *  2) the current node not being a child of the previous node
479          *      means there was a tag closing in the input html
480          */
481         if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
482             blockTagClosed = closeTag();
483             // visually collapse subsequent block tags, but if the element after the closed block tag
484             // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
485             // hasBlock to false.
486             if (blockTagClosed
487                 && !currentNode->isBlock()
488                 && currentNode->id != Html_unknown)
489             {
490                 hasBlock = false;
491             } else if (hasBlock) {
492                 // when collapsing subsequent block tags we need to clear the block format
493                 QTextBlockFormat blockFormat = currentNode->blockFormat;
494                 blockFormat.setIndent(indent);
495
496                 QTextBlockFormat oldFormat = cursor.blockFormat();
497                 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
498                     QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
499                     if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
500                         /* We remove an empty paragrah that requested a page break after.
501                            moving that request to the next paragraph means we also need to make
502                             that a pagebreak before to keep the same visual appearance.
503                         */
504                         pageBreak = QTextFormat::PageBreak_AlwaysBefore;
505                     blockFormat.setPageBreakPolicy(pageBreak);
506                 }
507
508                 cursor.setBlockFormat(blockFormat);
509             }
510         }
511
512         if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
513             if (currentNode->id == Html_title)
514                 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
515             // ignore explicitly 'invisible' elements
516             continue;
517         }
518
519         if (processSpecialNodes() == ContinueWithNextNode)
520             continue;
521
522         // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
523         if (blockTagClosed
524             && !hasBlock
525             && !currentNode->isBlock()
526             && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
527             && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
528
529             QTextBlockFormat block = currentNode->blockFormat;
530             block.setIndent(indent);
531
532             appendBlock(block, currentNode->charFormat);
533
534             hasBlock = true;
535         }
536
537         if (currentNode->isBlock()) {
538             QTextHtmlImporter::ProcessNodeResult result = processBlockNode();
539             if (result == ContinueWithNextNode) {
540                 continue;
541             } else if (result == ContinueWithNextSibling) {
542                 currentNodeIdx += currentNode->children.size();
543                 continue;
544             }
545         }
546
547         if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
548             namedAnchors.append(currentNode->charFormat.anchorName());
549         }
550
551         if (appendNodeText())
552             hasBlock = false; // if we actually appended text then we don't
553                               // have an empty block anymore
554     }
555
556     cursor.endEditBlock();
557 }
558
559 bool QTextHtmlImporter::appendNodeText()
560 {
561     const int initialCursorPosition = cursor.position();
562     QTextCharFormat format = currentNode->charFormat;
563
564     if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
565         compressNextWhitespace = PreserveWhiteSpace;
566
567     QString text = currentNode->text;
568
569     QString textToInsert;
570     textToInsert.reserve(text.size());
571
572     for (int i = 0; i < text.length(); ++i) {
573         QChar ch = text.at(i);
574
575         if (ch.isSpace()
576             && ch != QChar::Nbsp
577             && ch != QChar::ParagraphSeparator) {
578
579             if (compressNextWhitespace == CollapseWhiteSpace)
580                 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
581             else if(compressNextWhitespace == RemoveWhiteSpace)
582                 continue;
583
584             if (wsm == QTextHtmlParserNode::WhiteSpacePre
585                 || textEditMode
586                ) {
587                 if (ch == QLatin1Char('\n')) {
588                     if (textEditMode)
589                         continue;
590                 } else if (ch == QLatin1Char('\r')) {
591                     continue;
592                 }
593             } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
594                 compressNextWhitespace = RemoveWhiteSpace;
595                 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
596                     ch = QChar::Nbsp;
597                 else
598                     ch = QLatin1Char(' ');
599             }
600         } else {
601             compressNextWhitespace = PreserveWhiteSpace;
602         }
603
604         if (ch == QLatin1Char('\n')
605             || ch == QChar::ParagraphSeparator) {
606
607             if (!textToInsert.isEmpty()) {
608                 cursor.insertText(textToInsert, format);
609                 textToInsert.clear();
610             }
611
612             QTextBlockFormat fmt = cursor.blockFormat();
613
614             if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
615                 QTextBlockFormat tmp = fmt;
616                 tmp.clearProperty(QTextFormat::BlockBottomMargin);
617                 cursor.setBlockFormat(tmp);
618             }
619
620             fmt.clearProperty(QTextFormat::BlockTopMargin);
621             appendBlock(fmt, cursor.charFormat());
622         } else {
623             if (!namedAnchors.isEmpty()) {
624                 if (!textToInsert.isEmpty()) {
625                     cursor.insertText(textToInsert, format);
626                     textToInsert.clear();
627                 }
628
629                 format.setAnchor(true);
630                 format.setAnchorNames(namedAnchors);
631                 cursor.insertText(ch, format);
632                 namedAnchors.clear();
633                 format.clearProperty(QTextFormat::IsAnchor);
634                 format.clearProperty(QTextFormat::AnchorName);
635             } else {
636                 textToInsert += ch;
637             }
638         }
639     }
640
641     if (!textToInsert.isEmpty()) {
642         cursor.insertText(textToInsert, format);
643     }
644
645     return cursor.position() != initialCursorPosition;
646 }
647
648 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
649 {
650     switch (currentNode->id) {
651         case Html_body:
652             if (currentNode->charFormat.background().style() != Qt::NoBrush) {
653                 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
654                 fmt.setBackground(currentNode->charFormat.background());
655                 doc->rootFrame()->setFrameFormat(fmt);
656                 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
657             }
658             compressNextWhitespace = RemoveWhiteSpace;
659             break;
660
661         case Html_ol:
662         case Html_ul: {
663             QTextListFormat::Style style = currentNode->listStyle;
664
665             if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
666                 const QTextHtmlParserNode *n = &at(currentNode->parent);
667                 while (n) {
668                     if (n->id == Html_ul) {
669                         style = nextListStyle(currentNode->listStyle);
670                     }
671                     if (n->parent)
672                         n = &at(n->parent);
673                     else
674                         n = 0;
675                 }
676             }
677
678             QTextListFormat listFmt;
679             listFmt.setStyle(style);
680             if (!currentNode->textListNumberPrefix.isNull())
681                 listFmt.setNumberPrefix(currentNode->textListNumberPrefix);
682             if (!currentNode->textListNumberSuffix.isNull())
683                 listFmt.setNumberSuffix(currentNode->textListNumberSuffix);
684
685             ++indent;
686             if (currentNode->hasCssListIndent)
687                 listFmt.setIndent(currentNode->cssListIndent);
688             else
689                 listFmt.setIndent(indent);
690
691             List l;
692             l.format = listFmt;
693             l.listNode = currentNodeIdx;
694             lists.append(l);
695             compressNextWhitespace = RemoveWhiteSpace;
696
697             // broken html: <ul>Text here<li>Foo
698             const QString simpl = currentNode->text.simplified();
699             if (simpl.isEmpty() || simpl.at(0).isSpace())
700                 return ContinueWithNextNode;
701             break;
702         }
703
704         case Html_table: {
705             Table t = scanTable(currentNodeIdx);
706             tables.append(t);
707             hasBlock = false;
708             compressNextWhitespace = RemoveWhiteSpace;
709             return ContinueWithNextNode;
710         }
711
712         case Html_tr:
713             return ContinueWithNextNode;
714
715         case Html_img: {
716             QTextImageFormat fmt;
717             fmt.setName(currentNode->imageName);
718
719             fmt.merge(currentNode->charFormat);
720
721             if (currentNode->imageWidth != -1)
722                 fmt.setWidth(currentNode->imageWidth);
723             if (currentNode->imageHeight != -1)
724                 fmt.setHeight(currentNode->imageHeight);
725
726             cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
727
728             cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
729             cursor.mergeCharFormat(currentNode->charFormat);
730             cursor.movePosition(QTextCursor::Right);
731             compressNextWhitespace = CollapseWhiteSpace;
732
733             hasBlock = false;
734             return ContinueWithNextNode;
735         }
736
737         case Html_hr: {
738             QTextBlockFormat blockFormat = currentNode->blockFormat;
739             blockFormat.setTopMargin(topMargin(currentNodeIdx));
740             blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
741             blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
742             if (hasBlock && importMode == ImportToDocument)
743                 cursor.mergeBlockFormat(blockFormat);
744             else
745                 appendBlock(blockFormat);
746             hasBlock = false;
747             compressNextWhitespace = RemoveWhiteSpace;
748             return ContinueWithNextNode;
749         }
750
751         default: break;
752     }
753     return ContinueWithCurrentNode;
754 }
755
756 // returns true if a block tag was closed
757 bool QTextHtmlImporter::closeTag()
758 {
759     const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
760     const int endDepth = depth(currentNodeIdx) - 1;
761     int depth = this->depth(currentNodeIdx - 1);
762     bool blockTagClosed = false;
763
764     while (depth > endDepth) {
765         Table *t = 0;
766         if (!tables.isEmpty())
767             t = &tables.last();
768
769         switch (closedNode->id) {
770             case Html_tr:
771                 if (t && !t->isTextFrame) {
772                     ++t->currentRow;
773
774                     // for broken html with rowspans but missing tr tags
775                     while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
776                         ++t->currentCell;
777                 }
778
779                 blockTagClosed = true;
780                 break;
781
782             case Html_table:
783                 if (!t)
784                     break;
785                 indent = t->lastIndent;
786
787                 tables.resize(tables.size() - 1);
788                 t = 0;
789
790                 if (tables.isEmpty()) {
791                     cursor = doc->rootFrame()->lastCursorPosition();
792                 } else {
793                     t = &tables.last();
794                     if (t->isTextFrame)
795                         cursor = t->frame->lastCursorPosition();
796                     else if (!t->currentCell.atEnd())
797                         cursor = t->currentCell.cell().lastCursorPosition();
798                 }
799
800                 // we don't need an extra block after tables, so we don't
801                 // claim to have closed one for the creation of a new one
802                 // in import()
803                 blockTagClosed = false;
804                 compressNextWhitespace = RemoveWhiteSpace;
805                 break;
806
807             case Html_th:
808             case Html_td:
809                 if (t && !t->isTextFrame)
810                     ++t->currentCell;
811                 blockTagClosed = true;
812                 compressNextWhitespace = RemoveWhiteSpace;
813                 break;
814
815             case Html_ol:
816             case Html_ul:
817                 if (lists.isEmpty())
818                     break;
819                 lists.resize(lists.size() - 1);
820                 --indent;
821                 blockTagClosed = true;
822                 break;
823
824             case Html_br:
825                 compressNextWhitespace = RemoveWhiteSpace;
826                 break;
827
828             case Html_div:
829                 if (closedNode->children.isEmpty())
830                     break;
831                 // fall through
832             default:
833                 if (closedNode->isBlock())
834                     blockTagClosed = true;
835                 break;
836         }
837
838         closedNode = &at(closedNode->parent);
839         --depth;
840     }
841
842     return blockTagClosed;
843 }
844
845 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
846 {
847     Table table;
848     table.columns = 0;
849
850     QVector<QTextLength> columnWidths;
851
852     int tableHeaderRowCount = 0;
853     QVector<int> rowNodes;
854     rowNodes.reserve(at(tableNodeIdx).children.count());
855     foreach (int row, at(tableNodeIdx).children)
856         switch (at(row).id) {
857             case Html_tr:
858                 rowNodes += row;
859                 break;
860             case Html_thead:
861             case Html_tbody:
862             case Html_tfoot:
863                 foreach (int potentialRow, at(row).children)
864                     if (at(potentialRow).id == Html_tr) {
865                         rowNodes += potentialRow;
866                         if (at(row).id == Html_thead)
867                             ++tableHeaderRowCount;
868                     }
869                 break;
870             default: break;
871         }
872
873     QVector<RowColSpanInfo> rowColSpans;
874     QVector<RowColSpanInfo> rowColSpanForColumn;
875
876     int effectiveRow = 0;
877     foreach (int row, rowNodes) {
878         int colsInRow = 0;
879
880         foreach (int cell, at(row).children)
881             if (at(cell).isTableCell()) {
882                 // skip all columns with spans from previous rows
883                 while (colsInRow < rowColSpanForColumn.size()) {
884                     const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
885
886                     if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
887                         Q_ASSERT(spanInfo.col == colsInRow);
888                         colsInRow += spanInfo.colSpan;
889                     } else
890                         break;
891                 }
892
893                 const QTextHtmlParserNode &c = at(cell);
894                 const int currentColumn = colsInRow;
895                 colsInRow += c.tableCellColSpan;
896
897                 RowColSpanInfo spanInfo;
898                 spanInfo.row = effectiveRow;
899                 spanInfo.col = currentColumn;
900                 spanInfo.colSpan = c.tableCellColSpan;
901                 spanInfo.rowSpan = c.tableCellRowSpan;
902                 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
903                     rowColSpans.append(spanInfo);
904
905                 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
906                 rowColSpanForColumn.resize(columnWidths.size());
907                 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
908                     if (columnWidths.at(i).type() == QTextLength::VariableLength) {
909                         QTextLength w = c.width;
910                         if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
911                             w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
912                         columnWidths[i] = w;
913                     }
914                     rowColSpanForColumn[i] = spanInfo;
915                 }
916             }
917
918         table.columns = qMax(table.columns, colsInRow);
919
920         ++effectiveRow;
921     }
922     table.rows = effectiveRow;
923
924     table.lastIndent = indent;
925     indent = 0;
926
927     if (table.rows == 0 || table.columns == 0)
928         return table;
929
930     QTextFrameFormat fmt;
931     const QTextHtmlParserNode &node = at(tableNodeIdx);
932
933     if (!node.isTextFrame) {
934         QTextTableFormat tableFmt;
935         tableFmt.setCellSpacing(node.tableCellSpacing);
936         tableFmt.setCellPadding(node.tableCellPadding);
937         if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
938             tableFmt.setAlignment(node.blockFormat.alignment());
939         tableFmt.setColumns(table.columns);
940         tableFmt.setColumnWidthConstraints(columnWidths);
941         tableFmt.setHeaderRowCount(tableHeaderRowCount);
942         fmt = tableFmt;
943     }
944
945     fmt.setTopMargin(topMargin(tableNodeIdx));
946     fmt.setBottomMargin(bottomMargin(tableNodeIdx));
947     fmt.setLeftMargin(leftMargin(tableNodeIdx)
948                       + table.lastIndent * 40 // ##### not a good emulation
949                       );
950     fmt.setRightMargin(rightMargin(tableNodeIdx));
951
952     // compatibility
953     if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
954         && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
955         && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
956         fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
957
958     fmt.setBorderStyle(node.borderStyle);
959     fmt.setBorderBrush(node.borderBrush);
960     fmt.setBorder(node.tableBorder);
961     fmt.setWidth(node.width);
962     fmt.setHeight(node.height);
963     if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
964         fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
965
966     if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
967         fmt.setLayoutDirection(node.blockFormat.layoutDirection());
968     if (node.charFormat.background().style() != Qt::NoBrush)
969         fmt.setBackground(node.charFormat.background());
970     fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
971
972     if (node.isTextFrame) {
973         if (node.isRootFrame) {
974             table.frame = cursor.currentFrame();
975             table.frame->setFrameFormat(fmt);
976         } else
977             table.frame = cursor.insertFrame(fmt);
978
979         table.isTextFrame = true;
980     } else {
981         const int oldPos = cursor.position();
982         QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
983         table.frame = textTable;
984
985         for (int i = 0; i < rowColSpans.count(); ++i) {
986             const RowColSpanInfo &nfo = rowColSpans.at(i);
987             textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
988         }
989
990         table.currentCell = TableCellIterator(textTable);
991         cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
992     }
993     return table;
994 }
995
996 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
997 {
998     QTextBlockFormat block;
999     QTextCharFormat charFmt;
1000     bool modifiedBlockFormat = true;
1001     bool modifiedCharFormat = true;
1002
1003     if (currentNode->isTableCell() && !tables.isEmpty()) {
1004         Table &t = tables.last();
1005         if (!t.isTextFrame && !t.currentCell.atEnd()) {
1006             QTextTableCell cell = t.currentCell.cell();
1007             if (cell.isValid()) {
1008                 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
1009                 if (topPadding(currentNodeIdx) >= 0)
1010                     fmt.setTopPadding(topPadding(currentNodeIdx));
1011                 if (bottomPadding(currentNodeIdx) >= 0)
1012                     fmt.setBottomPadding(bottomPadding(currentNodeIdx));
1013                 if (leftPadding(currentNodeIdx) >= 0)
1014                     fmt.setLeftPadding(leftPadding(currentNodeIdx));
1015                 if (rightPadding(currentNodeIdx) >= 0)
1016                     fmt.setRightPadding(rightPadding(currentNodeIdx));
1017                 cell.setFormat(fmt);
1018
1019                 cursor.setPosition(cell.firstPosition());
1020             }
1021         }
1022         hasBlock = true;
1023         compressNextWhitespace = RemoveWhiteSpace;
1024
1025         if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1026             charFmt.setBackground(currentNode->charFormat.background());
1027             cursor.mergeBlockCharFormat(charFmt);
1028         }
1029     }
1030
1031     if (hasBlock) {
1032         block = cursor.blockFormat();
1033         charFmt = cursor.blockCharFormat();
1034         modifiedBlockFormat = false;
1035         modifiedCharFormat = false;
1036     }
1037
1038     // collapse
1039     {
1040         qreal tm = qreal(topMargin(currentNodeIdx));
1041         if (tm > block.topMargin()) {
1042             block.setTopMargin(tm);
1043             modifiedBlockFormat = true;
1044         }
1045     }
1046
1047     int bottomMargin = this->bottomMargin(currentNodeIdx);
1048
1049     // for list items we may want to collapse with the bottom margin of the
1050     // list.
1051     const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1052     if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1053         && parentNode
1054         && (parentNode->isListStart() || parentNode->id == Html_dl)
1055         && (parentNode->children.last() == currentNodeIdx)) {
1056         bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1057     }
1058
1059     if (block.bottomMargin() != bottomMargin) {
1060         block.setBottomMargin(bottomMargin);
1061         modifiedBlockFormat = true;
1062     }
1063
1064     {
1065         const qreal lm = leftMargin(currentNodeIdx);
1066         const qreal rm = rightMargin(currentNodeIdx);
1067
1068         if (block.leftMargin() != lm) {
1069             block.setLeftMargin(lm);
1070             modifiedBlockFormat = true;
1071         }
1072         if (block.rightMargin() != rm) {
1073             block.setRightMargin(rm);
1074             modifiedBlockFormat = true;
1075         }
1076     }
1077
1078     if (currentNode->id != Html_li
1079         && indent != 0
1080         && (lists.isEmpty()
1081             || !hasBlock
1082             || !lists.last().list
1083             || lists.last().list->itemNumber(cursor.block()) == -1
1084            )
1085        ) {
1086         block.setIndent(indent);
1087         modifiedBlockFormat = true;
1088     }
1089
1090     if (currentNode->blockFormat.propertyCount() > 0) {
1091         modifiedBlockFormat = true;
1092         block.merge(currentNode->blockFormat);
1093     }
1094
1095     if (currentNode->charFormat.propertyCount() > 0) {
1096         modifiedCharFormat = true;
1097         charFmt.merge(currentNode->charFormat);
1098     }
1099
1100     // ####################
1101     //                block.setFloatPosition(node->cssFloat);
1102
1103     if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1104         block.setNonBreakableLines(true);
1105         modifiedBlockFormat = true;
1106     }
1107
1108     if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1109         block.setBackground(currentNode->charFormat.background());
1110         modifiedBlockFormat = true;
1111     }
1112
1113     if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1114         if (modifiedBlockFormat)
1115             cursor.setBlockFormat(block);
1116         if (modifiedCharFormat)
1117             cursor.setBlockCharFormat(charFmt);
1118     } else {
1119         if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1120             cursor.setBlockFormat(block);
1121             cursor.setBlockCharFormat(charFmt);
1122         } else {
1123             appendBlock(block, charFmt);
1124         }
1125     }
1126
1127     if (currentNode->userState != -1)
1128         cursor.block().setUserState(currentNode->userState);
1129
1130     if (currentNode->id == Html_li && !lists.isEmpty()) {
1131         List &l = lists.last();
1132         if (l.list) {
1133             l.list->add(cursor.block());
1134         } else {
1135             l.list = cursor.createList(l.format);
1136             const qreal listTopMargin = topMargin(l.listNode);
1137             if (listTopMargin > block.topMargin()) {
1138                 block.setTopMargin(listTopMargin);
1139                 cursor.mergeBlockFormat(block);
1140             }
1141         }
1142         if (hasBlock) {
1143             QTextBlockFormat fmt;
1144             fmt.setIndent(0);
1145             cursor.mergeBlockFormat(fmt);
1146         }
1147     }
1148
1149     forceBlockMerging = false;
1150     if (currentNode->id == Html_body || currentNode->id == Html_html)
1151         forceBlockMerging = true;
1152
1153     if (currentNode->isEmptyParagraph) {
1154         hasBlock = false;
1155         return ContinueWithNextSibling;
1156     }
1157
1158     hasBlock = true;
1159     blockTagClosed = false;
1160     return ContinueWithCurrentNode;
1161 }
1162
1163 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1164 {
1165     if (!namedAnchors.isEmpty()) {
1166         charFmt.setAnchor(true);
1167         charFmt.setAnchorNames(namedAnchors);
1168         namedAnchors.clear();
1169     }
1170
1171     cursor.insertBlock(format, charFmt);
1172
1173     if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1174         compressNextWhitespace = RemoveWhiteSpace;
1175 }
1176
1177 #endif // QT_NO_TEXTHTMLPARSER
1178
1179 /*!
1180     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1181
1182     Returns a QTextDocumentFragment based on the arbitrary piece of
1183     HTML in the given \a text. The formatting is preserved as much as
1184     possible; for example, "<b>bold</b>" will become a document
1185     fragment with the text "bold" with a bold character format.
1186 */
1187
1188 #ifndef QT_NO_TEXTHTMLPARSER
1189
1190 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1191 {
1192     return fromHtml(html, 0);
1193 }
1194
1195 /*!
1196     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1197     \since 4.2
1198
1199     Returns a QTextDocumentFragment based on the arbitrary piece of
1200     HTML in the given \a text. The formatting is preserved as much as
1201     possible; for example, "<b>bold</b>" will become a document
1202     fragment with the text "bold" with a bold character format.
1203
1204     If the provided HTML contains references to external resources such as imported style sheets, then
1205     they will be loaded through the \a resourceProvider.
1206 */
1207
1208 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1209 {
1210     QTextDocumentFragment res;
1211     res.d = new QTextDocumentFragmentPrivate;
1212
1213     QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1214     importer.import();
1215     return res;
1216 }
1217
1218 QT_END_NAMESPACE
1219 #endif // QT_NO_TEXTHTMLPARSER