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