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

source code of qtbase/src/gui/text/qtextdocumentfragment.cpp