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