Coverage Report

Created: 2026-05-16 07:21

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