Export QTextImageHandler and add accessor for image
[profile/ivi/qtbase.git] / src / gui / text / qtextdocumentfragment.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtGui module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
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 // #### Qt 5: merge with other overload
376 /*!
377     \overload
378 */
379
380 #ifndef QT_NO_TEXTHTMLPARSER
381
382 QString QTextDocumentFragment::toHtml() const
383 {
384     return toHtml(QByteArray());
385 }
386
387 /*!
388     \since 4.2
389
390     Returns the contents of the document fragment as HTML,
391     using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
392
393     \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
394 */
395 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
396 {
397     if (!d)
398         return QString();
399
400     return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
401 }
402
403 #endif // QT_NO_TEXTHTMLPARSER
404
405 /*!
406     Returns a document fragment that contains the given \a plainText.
407
408     When inserting such a fragment into a QTextDocument the current char format of
409     the QTextCursor used for insertion is used as format for the text.
410 */
411 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
412 {
413     QTextDocumentFragment res;
414
415     res.d = new QTextDocumentFragmentPrivate;
416     res.d->importedFromPlainText = true;
417     QTextCursor cursor(res.d->doc);
418     cursor.insertText(plainText);
419     return res;
420 }
421
422 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
423 {
424     if (style == QTextListFormat::ListDisc)
425         return QTextListFormat::ListCircle;
426     else if (style == QTextListFormat::ListCircle)
427         return QTextListFormat::ListSquare;
428     return style;
429 }
430
431 #ifndef QT_NO_TEXTHTMLPARSER
432
433 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
434     : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
435 {
436     cursor = QTextCursor(doc);
437     wsm = QTextHtmlParserNode::WhiteSpaceNormal;
438
439     QString html = _html;
440     const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
441     if (startFragmentPos != -1) {
442         QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
443
444         // Hack for Qt3
445         const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
446
447         const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
448         if (startFragmentPos < endFragmentPos)
449             html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
450         else
451             html = html.mid(startFragmentPos);
452
453         if (hasQtRichtextMetaTag)
454             html.prepend(qt3RichTextHeader);
455     }
456
457     parse(html, resourceProvider ? resourceProvider : doc);
458 //    dumpHtml();
459 }
460
461 void QTextHtmlImporter::import()
462 {
463     cursor.beginEditBlock();
464     hasBlock = true;
465     forceBlockMerging = false;
466     compressNextWhitespace = RemoveWhiteSpace;
467     blockTagClosed = false;
468     for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
469         currentNode = &at(currentNodeIdx);
470         wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
471
472         /*
473          * process each node in three stages:
474          * 1) check if the hierarchy changed and we therefore passed the
475          *    equivalent of a closing tag -> we may need to finish off
476          *    some structures like tables
477          *
478          * 2) check if the current node is a special node like a
479          *    <table>, <ul> or <img> tag that requires special processing
480          *
481          * 3) if the node should result in a QTextBlock create one and
482          *    finally insert text that may be attached to the node
483          */
484
485         /* emit 'closing' table blocks or adjust current indent level
486          * if we
487          *  1) are beyond the first node
488          *  2) the current node not being a child of the previous node
489          *      means there was a tag closing in the input html
490          */
491         if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
492             blockTagClosed = closeTag();
493             // visually collapse subsequent block tags, but if the element after the closed block tag
494             // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
495             // hasBlock to false.
496             if (blockTagClosed
497                 && !currentNode->isBlock()
498                 && currentNode->id != Html_unknown)
499             {
500                 hasBlock = false;
501             } else if (hasBlock) {
502                 // when collapsing subsequent block tags we need to clear the block format
503                 QTextBlockFormat blockFormat = currentNode->blockFormat;
504                 blockFormat.setIndent(indent);
505
506                 QTextBlockFormat oldFormat = cursor.blockFormat();
507                 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
508                     QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
509                     if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
510                         /* We remove an empty paragrah that requested a page break after.
511                            moving that request to the next paragraph means we also need to make
512                             that a pagebreak before to keep the same visual appearance.
513                         */
514                         pageBreak = QTextFormat::PageBreak_AlwaysBefore;
515                     blockFormat.setPageBreakPolicy(pageBreak);
516                 }
517
518                 cursor.setBlockFormat(blockFormat);
519             }
520         }
521
522         if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
523             if (currentNode->id == Html_title)
524                 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
525             // ignore explicitly 'invisible' elements
526             continue;
527         }
528
529         if (processSpecialNodes() == ContinueWithNextNode)
530             continue;
531
532         // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
533         if (blockTagClosed
534             && !hasBlock
535             && !currentNode->isBlock()
536             && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
537             && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
538
539             QTextBlockFormat block = currentNode->blockFormat;
540             block.setIndent(indent);
541
542             appendBlock(block, currentNode->charFormat);
543
544             hasBlock = true;
545         }
546
547         if (currentNode->isBlock()) {
548             QTextHtmlImporter::ProcessNodeResult result = processBlockNode();
549             if (result == ContinueWithNextNode) {
550                 continue;
551             } else if (result == ContinueWithNextSibling) {
552                 currentNodeIdx += currentNode->children.size();
553                 continue;
554             }
555         }
556
557         if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
558             namedAnchors.append(currentNode->charFormat.anchorName());
559         }
560
561         if (appendNodeText())
562             hasBlock = false; // if we actually appended text then we don't
563                               // have an empty block anymore
564     }
565
566     cursor.endEditBlock();
567 }
568
569 bool QTextHtmlImporter::appendNodeText()
570 {
571     const int initialCursorPosition = cursor.position();
572     QTextCharFormat format = currentNode->charFormat;
573
574     if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
575         compressNextWhitespace = PreserveWhiteSpace;
576
577     QString text = currentNode->text;
578
579     QString textToInsert;
580     textToInsert.reserve(text.size());
581
582     for (int i = 0; i < text.length(); ++i) {
583         QChar ch = text.at(i);
584
585         if (ch.isSpace()
586             && ch != QChar::Nbsp
587             && ch != QChar::ParagraphSeparator) {
588
589             if (compressNextWhitespace == CollapseWhiteSpace)
590                 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
591             else if(compressNextWhitespace == RemoveWhiteSpace)
592                 continue;
593
594             if (wsm == QTextHtmlParserNode::WhiteSpacePre
595                 || textEditMode
596                ) {
597                 if (ch == QLatin1Char('\n')) {
598                     if (textEditMode)
599                         continue;
600                 } else if (ch == QLatin1Char('\r')) {
601                     continue;
602                 }
603             } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
604                 compressNextWhitespace = RemoveWhiteSpace;
605                 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
606                     ch = QChar::Nbsp;
607                 else
608                     ch = QLatin1Char(' ');
609             }
610         } else {
611             compressNextWhitespace = PreserveWhiteSpace;
612         }
613
614         if (ch == QLatin1Char('\n')
615             || ch == QChar::ParagraphSeparator) {
616
617             if (!textToInsert.isEmpty()) {
618                 cursor.insertText(textToInsert, format);
619                 textToInsert.clear();
620             }
621
622             QTextBlockFormat fmt = cursor.blockFormat();
623
624             if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
625                 QTextBlockFormat tmp = fmt;
626                 tmp.clearProperty(QTextFormat::BlockBottomMargin);
627                 cursor.setBlockFormat(tmp);
628             }
629
630             fmt.clearProperty(QTextFormat::BlockTopMargin);
631             appendBlock(fmt, cursor.charFormat());
632         } else {
633             if (!namedAnchors.isEmpty()) {
634                 if (!textToInsert.isEmpty()) {
635                     cursor.insertText(textToInsert, format);
636                     textToInsert.clear();
637                 }
638
639                 format.setAnchor(true);
640                 format.setAnchorNames(namedAnchors);
641                 cursor.insertText(ch, format);
642                 namedAnchors.clear();
643                 format.clearProperty(QTextFormat::IsAnchor);
644                 format.clearProperty(QTextFormat::AnchorName);
645             } else {
646                 textToInsert += ch;
647             }
648         }
649     }
650
651     if (!textToInsert.isEmpty()) {
652         cursor.insertText(textToInsert, format);
653     }
654
655     return cursor.position() != initialCursorPosition;
656 }
657
658 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
659 {
660     switch (currentNode->id) {
661         case Html_body:
662             if (currentNode->charFormat.background().style() != Qt::NoBrush) {
663                 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
664                 fmt.setBackground(currentNode->charFormat.background());
665                 doc->rootFrame()->setFrameFormat(fmt);
666                 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
667             }
668             compressNextWhitespace = RemoveWhiteSpace;
669             break;
670
671         case Html_ol:
672         case Html_ul: {
673             QTextListFormat::Style style = currentNode->listStyle;
674
675             if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
676                 const QTextHtmlParserNode *n = &at(currentNode->parent);
677                 while (n) {
678                     if (n->id == Html_ul) {
679                         style = nextListStyle(currentNode->listStyle);
680                     }
681                     if (n->parent)
682                         n = &at(n->parent);
683                     else
684                         n = 0;
685                 }
686             }
687
688             QTextListFormat listFmt;
689             listFmt.setStyle(style);
690             if (!currentNode->textListNumberPrefix.isNull())
691                 listFmt.setNumberPrefix(currentNode->textListNumberPrefix);
692             if (!currentNode->textListNumberSuffix.isNull())
693                 listFmt.setNumberSuffix(currentNode->textListNumberSuffix);
694
695             ++indent;
696             if (currentNode->hasCssListIndent)
697                 listFmt.setIndent(currentNode->cssListIndent);
698             else
699                 listFmt.setIndent(indent);
700
701             List l;
702             l.format = listFmt;
703             l.listNode = currentNodeIdx;
704             lists.append(l);
705             compressNextWhitespace = RemoveWhiteSpace;
706
707             // broken html: <ul>Text here<li>Foo
708             const QString simpl = currentNode->text.simplified();
709             if (simpl.isEmpty() || simpl.at(0).isSpace())
710                 return ContinueWithNextNode;
711             break;
712         }
713
714         case Html_table: {
715             Table t = scanTable(currentNodeIdx);
716             tables.append(t);
717             hasBlock = false;
718             compressNextWhitespace = RemoveWhiteSpace;
719             return ContinueWithNextNode;
720         }
721
722         case Html_tr:
723             return ContinueWithNextNode;
724
725         case Html_img: {
726             QTextImageFormat fmt;
727             fmt.setName(currentNode->imageName);
728
729             fmt.merge(currentNode->charFormat);
730
731             if (currentNode->imageWidth != -1)
732                 fmt.setWidth(currentNode->imageWidth);
733             if (currentNode->imageHeight != -1)
734                 fmt.setHeight(currentNode->imageHeight);
735
736             cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
737
738             cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
739             cursor.mergeCharFormat(currentNode->charFormat);
740             cursor.movePosition(QTextCursor::Right);
741             compressNextWhitespace = CollapseWhiteSpace;
742
743             hasBlock = false;
744             return ContinueWithNextNode;
745         }
746
747         case Html_hr: {
748             QTextBlockFormat blockFormat = currentNode->blockFormat;
749             blockFormat.setTopMargin(topMargin(currentNodeIdx));
750             blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
751             blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
752             if (hasBlock && importMode == ImportToDocument)
753                 cursor.mergeBlockFormat(blockFormat);
754             else
755                 appendBlock(blockFormat);
756             hasBlock = false;
757             compressNextWhitespace = RemoveWhiteSpace;
758             return ContinueWithNextNode;
759         }
760
761         default: break;
762     }
763     return ContinueWithCurrentNode;
764 }
765
766 // returns true if a block tag was closed
767 bool QTextHtmlImporter::closeTag()
768 {
769     const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
770     const int endDepth = depth(currentNodeIdx) - 1;
771     int depth = this->depth(currentNodeIdx - 1);
772     bool blockTagClosed = false;
773
774     while (depth > endDepth) {
775         Table *t = 0;
776         if (!tables.isEmpty())
777             t = &tables.last();
778
779         switch (closedNode->id) {
780             case Html_tr:
781                 if (t && !t->isTextFrame) {
782                     ++t->currentRow;
783
784                     // for broken html with rowspans but missing tr tags
785                     while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
786                         ++t->currentCell;
787                 }
788
789                 blockTagClosed = true;
790                 break;
791
792             case Html_table:
793                 if (!t)
794                     break;
795                 indent = t->lastIndent;
796
797                 tables.resize(tables.size() - 1);
798                 t = 0;
799
800                 if (tables.isEmpty()) {
801                     cursor = doc->rootFrame()->lastCursorPosition();
802                 } else {
803                     t = &tables.last();
804                     if (t->isTextFrame)
805                         cursor = t->frame->lastCursorPosition();
806                     else if (!t->currentCell.atEnd())
807                         cursor = t->currentCell.cell().lastCursorPosition();
808                 }
809
810                 // we don't need an extra block after tables, so we don't
811                 // claim to have closed one for the creation of a new one
812                 // in import()
813                 blockTagClosed = false;
814                 compressNextWhitespace = RemoveWhiteSpace;
815                 break;
816
817             case Html_th:
818             case Html_td:
819                 if (t && !t->isTextFrame)
820                     ++t->currentCell;
821                 blockTagClosed = true;
822                 compressNextWhitespace = RemoveWhiteSpace;
823                 break;
824
825             case Html_ol:
826             case Html_ul:
827                 if (lists.isEmpty())
828                     break;
829                 lists.resize(lists.size() - 1);
830                 --indent;
831                 blockTagClosed = true;
832                 break;
833
834             case Html_br:
835                 compressNextWhitespace = RemoveWhiteSpace;
836                 break;
837
838             case Html_div:
839                 if (closedNode->children.isEmpty())
840                     break;
841                 // fall through
842             default:
843                 if (closedNode->isBlock())
844                     blockTagClosed = true;
845                 break;
846         }
847
848         closedNode = &at(closedNode->parent);
849         --depth;
850     }
851
852     return blockTagClosed;
853 }
854
855 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
856 {
857     Table table;
858     table.columns = 0;
859
860     QVector<QTextLength> columnWidths;
861
862     int tableHeaderRowCount = 0;
863     QVector<int> rowNodes;
864     rowNodes.reserve(at(tableNodeIdx).children.count());
865     foreach (int row, at(tableNodeIdx).children)
866         switch (at(row).id) {
867             case Html_tr:
868                 rowNodes += row;
869                 break;
870             case Html_thead:
871             case Html_tbody:
872             case Html_tfoot:
873                 foreach (int potentialRow, at(row).children)
874                     if (at(potentialRow).id == Html_tr) {
875                         rowNodes += potentialRow;
876                         if (at(row).id == Html_thead)
877                             ++tableHeaderRowCount;
878                     }
879                 break;
880             default: break;
881         }
882
883     QVector<RowColSpanInfo> rowColSpans;
884     QVector<RowColSpanInfo> rowColSpanForColumn;
885
886     int effectiveRow = 0;
887     foreach (int row, rowNodes) {
888         int colsInRow = 0;
889
890         foreach (int cell, at(row).children)
891             if (at(cell).isTableCell()) {
892                 // skip all columns with spans from previous rows
893                 while (colsInRow < rowColSpanForColumn.size()) {
894                     const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
895
896                     if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
897                         Q_ASSERT(spanInfo.col == colsInRow);
898                         colsInRow += spanInfo.colSpan;
899                     } else
900                         break;
901                 }
902
903                 const QTextHtmlParserNode &c = at(cell);
904                 const int currentColumn = colsInRow;
905                 colsInRow += c.tableCellColSpan;
906
907                 RowColSpanInfo spanInfo;
908                 spanInfo.row = effectiveRow;
909                 spanInfo.col = currentColumn;
910                 spanInfo.colSpan = c.tableCellColSpan;
911                 spanInfo.rowSpan = c.tableCellRowSpan;
912                 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
913                     rowColSpans.append(spanInfo);
914
915                 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
916                 rowColSpanForColumn.resize(columnWidths.size());
917                 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
918                     if (columnWidths.at(i).type() == QTextLength::VariableLength) {
919                         QTextLength w = c.width;
920                         if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
921                             w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
922                         columnWidths[i] = w;
923                     }
924                     rowColSpanForColumn[i] = spanInfo;
925                 }
926             }
927
928         table.columns = qMax(table.columns, colsInRow);
929
930         ++effectiveRow;
931     }
932     table.rows = effectiveRow;
933
934     table.lastIndent = indent;
935     indent = 0;
936
937     if (table.rows == 0 || table.columns == 0)
938         return table;
939
940     QTextFrameFormat fmt;
941     const QTextHtmlParserNode &node = at(tableNodeIdx);
942
943     if (!node.isTextFrame) {
944         QTextTableFormat tableFmt;
945         tableFmt.setCellSpacing(node.tableCellSpacing);
946         tableFmt.setCellPadding(node.tableCellPadding);
947         if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
948             tableFmt.setAlignment(node.blockFormat.alignment());
949         tableFmt.setColumns(table.columns);
950         tableFmt.setColumnWidthConstraints(columnWidths);
951         tableFmt.setHeaderRowCount(tableHeaderRowCount);
952         fmt = tableFmt;
953     }
954
955     fmt.setTopMargin(topMargin(tableNodeIdx));
956     fmt.setBottomMargin(bottomMargin(tableNodeIdx));
957     fmt.setLeftMargin(leftMargin(tableNodeIdx)
958                       + table.lastIndent * 40 // ##### not a good emulation
959                       );
960     fmt.setRightMargin(rightMargin(tableNodeIdx));
961
962     // compatibility
963     if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
964         && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
965         && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
966         fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
967
968     fmt.setBorderStyle(node.borderStyle);
969     fmt.setBorderBrush(node.borderBrush);
970     fmt.setBorder(node.tableBorder);
971     fmt.setWidth(node.width);
972     fmt.setHeight(node.height);
973     if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
974         fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
975
976     if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
977         fmt.setLayoutDirection(node.blockFormat.layoutDirection());
978     if (node.charFormat.background().style() != Qt::NoBrush)
979         fmt.setBackground(node.charFormat.background());
980     fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
981
982     if (node.isTextFrame) {
983         if (node.isRootFrame) {
984             table.frame = cursor.currentFrame();
985             table.frame->setFrameFormat(fmt);
986         } else
987             table.frame = cursor.insertFrame(fmt);
988
989         table.isTextFrame = true;
990     } else {
991         const int oldPos = cursor.position();
992         QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
993         table.frame = textTable;
994
995         for (int i = 0; i < rowColSpans.count(); ++i) {
996             const RowColSpanInfo &nfo = rowColSpans.at(i);
997             textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
998         }
999
1000         table.currentCell = TableCellIterator(textTable);
1001         cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
1002     }
1003     return table;
1004 }
1005
1006 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
1007 {
1008     QTextBlockFormat block;
1009     QTextCharFormat charFmt;
1010     bool modifiedBlockFormat = true;
1011     bool modifiedCharFormat = true;
1012
1013     if (currentNode->isTableCell() && !tables.isEmpty()) {
1014         Table &t = tables.last();
1015         if (!t.isTextFrame && !t.currentCell.atEnd()) {
1016             QTextTableCell cell = t.currentCell.cell();
1017             if (cell.isValid()) {
1018                 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
1019                 if (topPadding(currentNodeIdx) >= 0)
1020                     fmt.setTopPadding(topPadding(currentNodeIdx));
1021                 if (bottomPadding(currentNodeIdx) >= 0)
1022                     fmt.setBottomPadding(bottomPadding(currentNodeIdx));
1023                 if (leftPadding(currentNodeIdx) >= 0)
1024                     fmt.setLeftPadding(leftPadding(currentNodeIdx));
1025                 if (rightPadding(currentNodeIdx) >= 0)
1026                     fmt.setRightPadding(rightPadding(currentNodeIdx));
1027                 cell.setFormat(fmt);
1028
1029                 cursor.setPosition(cell.firstPosition());
1030             }
1031         }
1032         hasBlock = true;
1033         compressNextWhitespace = RemoveWhiteSpace;
1034
1035         if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1036             charFmt.setBackground(currentNode->charFormat.background());
1037             cursor.mergeBlockCharFormat(charFmt);
1038         }
1039     }
1040
1041     if (hasBlock) {
1042         block = cursor.blockFormat();
1043         charFmt = cursor.blockCharFormat();
1044         modifiedBlockFormat = false;
1045         modifiedCharFormat = false;
1046     }
1047
1048     // collapse
1049     {
1050         qreal tm = qreal(topMargin(currentNodeIdx));
1051         if (tm > block.topMargin()) {
1052             block.setTopMargin(tm);
1053             modifiedBlockFormat = true;
1054         }
1055     }
1056
1057     int bottomMargin = this->bottomMargin(currentNodeIdx);
1058
1059     // for list items we may want to collapse with the bottom margin of the
1060     // list.
1061     const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1062     if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1063         && parentNode
1064         && (parentNode->isListStart() || parentNode->id == Html_dl)
1065         && (parentNode->children.last() == currentNodeIdx)) {
1066         bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1067     }
1068
1069     if (block.bottomMargin() != bottomMargin) {
1070         block.setBottomMargin(bottomMargin);
1071         modifiedBlockFormat = true;
1072     }
1073
1074     {
1075         const qreal lm = leftMargin(currentNodeIdx);
1076         const qreal rm = rightMargin(currentNodeIdx);
1077
1078         if (block.leftMargin() != lm) {
1079             block.setLeftMargin(lm);
1080             modifiedBlockFormat = true;
1081         }
1082         if (block.rightMargin() != rm) {
1083             block.setRightMargin(rm);
1084             modifiedBlockFormat = true;
1085         }
1086     }
1087
1088     if (currentNode->id != Html_li
1089         && indent != 0
1090         && (lists.isEmpty()
1091             || !hasBlock
1092             || !lists.last().list
1093             || lists.last().list->itemNumber(cursor.block()) == -1
1094            )
1095        ) {
1096         block.setIndent(indent);
1097         modifiedBlockFormat = true;
1098     }
1099
1100     if (currentNode->blockFormat.propertyCount() > 0) {
1101         modifiedBlockFormat = true;
1102         block.merge(currentNode->blockFormat);
1103     }
1104
1105     if (currentNode->charFormat.propertyCount() > 0) {
1106         modifiedCharFormat = true;
1107         charFmt.merge(currentNode->charFormat);
1108     }
1109
1110     // ####################
1111     //                block.setFloatPosition(node->cssFloat);
1112
1113     if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1114         block.setNonBreakableLines(true);
1115         modifiedBlockFormat = true;
1116     }
1117
1118     if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1119         block.setBackground(currentNode->charFormat.background());
1120         modifiedBlockFormat = true;
1121     }
1122
1123     if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1124         if (modifiedBlockFormat)
1125             cursor.setBlockFormat(block);
1126         if (modifiedCharFormat)
1127             cursor.setBlockCharFormat(charFmt);
1128     } else {
1129         if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1130             cursor.setBlockFormat(block);
1131             cursor.setBlockCharFormat(charFmt);
1132         } else {
1133             appendBlock(block, charFmt);
1134         }
1135     }
1136
1137     if (currentNode->userState != -1)
1138         cursor.block().setUserState(currentNode->userState);
1139
1140     if (currentNode->id == Html_li && !lists.isEmpty()) {
1141         List &l = lists.last();
1142         if (l.list) {
1143             l.list->add(cursor.block());
1144         } else {
1145             l.list = cursor.createList(l.format);
1146             const qreal listTopMargin = topMargin(l.listNode);
1147             if (listTopMargin > block.topMargin()) {
1148                 block.setTopMargin(listTopMargin);
1149                 cursor.mergeBlockFormat(block);
1150             }
1151         }
1152         if (hasBlock) {
1153             QTextBlockFormat fmt;
1154             fmt.setIndent(0);
1155             cursor.mergeBlockFormat(fmt);
1156         }
1157     }
1158
1159     forceBlockMerging = false;
1160     if (currentNode->id == Html_body || currentNode->id == Html_html)
1161         forceBlockMerging = true;
1162
1163     if (currentNode->isEmptyParagraph) {
1164         hasBlock = false;
1165         return ContinueWithNextSibling;
1166     }
1167
1168     hasBlock = true;
1169     blockTagClosed = false;
1170     return ContinueWithCurrentNode;
1171 }
1172
1173 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1174 {
1175     if (!namedAnchors.isEmpty()) {
1176         charFmt.setAnchor(true);
1177         charFmt.setAnchorNames(namedAnchors);
1178         namedAnchors.clear();
1179     }
1180
1181     cursor.insertBlock(format, charFmt);
1182
1183     if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1184         compressNextWhitespace = RemoveWhiteSpace;
1185 }
1186
1187 #endif // QT_NO_TEXTHTMLPARSER
1188
1189 /*!
1190     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1191
1192     Returns a QTextDocumentFragment based on the arbitrary piece of
1193     HTML in the given \a text. The formatting is preserved as much as
1194     possible; for example, "<b>bold</b>" will become a document
1195     fragment with the text "bold" with a bold character format.
1196 */
1197
1198 #ifndef QT_NO_TEXTHTMLPARSER
1199
1200 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1201 {
1202     return fromHtml(html, 0);
1203 }
1204
1205 /*!
1206     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1207     \since 4.2
1208
1209     Returns a QTextDocumentFragment based on the arbitrary piece of
1210     HTML in the given \a text. The formatting is preserved as much as
1211     possible; for example, "<b>bold</b>" will become a document
1212     fragment with the text "bold" with a bold character format.
1213
1214     If the provided HTML contains references to external resources such as imported style sheets, then
1215     they will be loaded through the \a resourceProvider.
1216 */
1217
1218 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1219 {
1220     QTextDocumentFragment res;
1221     res.d = new QTextDocumentFragmentPrivate;
1222
1223     QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1224     importer.import();
1225     return res;
1226 }
1227
1228 QT_END_NAMESPACE
1229 #endif // QT_NO_TEXTHTMLPARSER