~oif-team/ubuntu/natty/qt4-x11/xi2.1

« back to all changes in this revision

Viewing changes to src/gui/text/qtextdocumentfragment.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-08-24 04:09:09 UTC
  • Revision ID: james.westby@ubuntu.com-20050824040909-xmxe9jfr4a0w5671
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
 
4
**
 
5
** This file is part of the text module of the Qt Toolkit.
 
6
**
 
7
** This file may be distributed under the terms of the Q Public License
 
8
** as defined by Trolltech AS of Norway and appearing in the file
 
9
** LICENSE.QPL included in the packaging of this file.
 
10
**
 
11
** This file may be distributed and/or modified under the terms of the
 
12
** GNU General Public License version 2 as published by the Free Software
 
13
** Foundation and appearing in the file LICENSE.GPL included in the
 
14
** packaging of this file.
 
15
**
 
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
 
17
**   information about Qt Commercial License Agreements.
 
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
 
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
 
20
**
 
21
** Contact info@trolltech.com if any conditions of this licensing are
 
22
** not clear to you.
 
23
**
 
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
26
**
 
27
****************************************************************************/
 
28
 
 
29
 
 
30
#include "qtextdocumentfragment.h"
 
31
#include "qtextdocumentfragment_p.h"
 
32
#include "qtextcursor_p.h"
 
33
#include "qtexttable.h"
 
34
 
 
35
#include <qdebug.h>
 
36
#include <qtextcodec.h>
 
37
#include <qbytearray.h>
 
38
#include <qdatastream.h>
 
39
 
 
40
QTextImportHelper::QTextImportHelper(QTextDocumentFragmentPrivate *docFragment, QTextDocumentPrivate *priv)
 
41
    : formatCollection(docFragment->formatCollection), originalText(priv->buffer())
 
42
{
 
43
    this->docFragment = docFragment;
 
44
    this->priv = priv;
 
45
}
 
46
 
 
47
int QTextImportHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
 
48
{
 
49
    QTextFormat fmt = oldFormat;
 
50
    if (objectIndexToSet != -1) {
 
51
        fmt.setObjectIndex(objectIndexToSet);
 
52
    } else if (fmt.objectIndex() != -1) {
 
53
        int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
 
54
        if (newObjectIndex == -1) {
 
55
            QTextFormat objFormat = priv->formatCollection()->objectFormat(fmt.objectIndex());
 
56
            Q_ASSERT(objFormat.objectIndex() == -1);
 
57
            newObjectIndex = formatCollection.createObjectIndex(objFormat);
 
58
            objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
 
59
        }
 
60
        fmt.setObjectIndex(newObjectIndex);
 
61
    }
 
62
    return formatCollection.indexForFormat(fmt);
 
63
}
 
64
 
 
65
int QTextImportHelper::appendFragment(int pos, int endPos, int objectIndex)
 
66
{
 
67
    QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos);
 
68
    const QTextFragmentData * const frag = fragIt.value();
 
69
 
 
70
    Q_ASSERT(objectIndex == -1
 
71
             || (frag->size == 1 && priv->formatCollection()->format(frag->format).objectIndex() != -1));
 
72
 
 
73
    const int charFormatIndex = convertFormatIndex(frag->format, objectIndex);
 
74
 
 
75
    const int inFragmentOffset = qMax(0, pos - fragIt.position());
 
76
    int charsToCopy = qMin(int(frag->size - inFragmentOffset), endPos - pos);
 
77
 
 
78
    QTextBlock nextBlock = priv->blocksFind(pos + 1);
 
79
 
 
80
    int blockIdx = -2;
 
81
    if (nextBlock.position() == pos + 1) {
 
82
        blockIdx = convertFormatIndex(nextBlock.blockFormat());
 
83
    } else // #### the initial paragraph doesn't have a dedicated text fragment, so
 
84
           // we have to copy it manually. QTextDocumentFragmentPrivate::insert will take
 
85
           // care of replacing an existing initial paragraph at insertion time with the one
 
86
           // we create here. remove this hack as soon as the piecetable is fixed.
 
87
        if (pos == 0 && docFragment->containsCompleteDocument) {
 
88
        docFragment->appendText(QString(QChar::ParagraphSeparator),
 
89
                                charFormatIndex, convertFormatIndex(priv->blocksBegin().blockFormat()));
 
90
    }
 
91
 
 
92
    docFragment->appendText(QString(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy),
 
93
                            charFormatIndex, blockIdx);
 
94
    return charsToCopy;
 
95
}
 
96
 
 
97
void QTextImportHelper::appendFragments(int pos, int endPos)
 
98
{
 
99
    Q_ASSERT(pos < endPos);
 
100
 
 
101
    while (pos < endPos)
 
102
        pos += appendFragment(pos, endPos);
 
103
}
 
104
 
 
105
QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &cursor)
 
106
    : hasTitle(false), containsCompleteDocument(false), setMarkerForHtmlExport(false)
 
107
{
 
108
    if (!cursor.hasSelection())
 
109
        return;
 
110
 
 
111
    QTextDocumentPrivate *priv = cursor.d->priv;
 
112
    QTextImportHelper importHelper(this, priv);
 
113
 
 
114
    if (cursor.selectionStart() == 0 && cursor.selectionEnd() == priv->length() - 1) {
 
115
        containsCompleteDocument = true;
 
116
        rootFrameFormat = priv->rootFrame()->frameFormat();
 
117
    }
 
118
 
 
119
    if (cursor.hasComplexSelection()) {
 
120
        QTextTable *table = cursor.currentTable();
 
121
        int row_start, col_start, num_rows, num_cols;
 
122
        cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
 
123
 
 
124
        QTextTableFormat tableFormat = table->format();
 
125
        tableFormat.setColumns(num_cols);
 
126
        tableFormat.clearColumnWidthConstraints();
 
127
        const int objectIndex = formatCollection.createObjectIndex(tableFormat);
 
128
 
 
129
        Q_ASSERT(row_start != -1);
 
130
        for (int r = row_start; r < row_start + num_rows; ++r) {
 
131
            for (int c = col_start; c < col_start + num_cols; ++c) {
 
132
                QTextTableCell cell = table->cellAt(r, c);
 
133
                const int rspan = cell.rowSpan();
 
134
                const int cspan = cell.columnSpan();
 
135
                if (rspan != 1) {
 
136
                    int cr = cell.row();
 
137
                    if (cr != r)
 
138
                        continue;
 
139
                }
 
140
                if (cspan != 1) {
 
141
                    int cc = cell.column();
 
142
                    if (cc != c)
 
143
                        continue;
 
144
                }
 
145
 
 
146
                // add the QTextBeginningOfFrame
 
147
                QTextCharFormat cellFormat = cell.format();
 
148
                if (r + rspan >= row_start + num_rows) {
 
149
                    cellFormat.setTableCellRowSpan(row_start + num_rows - r);
 
150
                }
 
151
                if (c + cspan >= col_start + num_cols) {
 
152
                    cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
 
153
                }
 
154
                const int charFormatIndex = importHelper.convertFormatIndex(cellFormat, objectIndex);
 
155
 
 
156
                int blockIdx = -2;
 
157
                const int cellPos = cell.firstPosition();
 
158
                QTextBlock block = priv->blocksFind(cellPos);
 
159
                if (block.position() == cellPos) {
 
160
                    blockIdx = importHelper.convertFormatIndex(block.blockFormat());
 
161
                }
 
162
 
 
163
                appendText(QString(QTextBeginningOfFrame), charFormatIndex, blockIdx);
 
164
 
 
165
                // nothing to add for empty cells
 
166
                if (cell.lastPosition() > cellPos) {
 
167
                    // add the contents
 
168
                    importHelper.appendFragments(cellPos, cell.lastPosition());
 
169
                }
 
170
            }
 
171
        }
 
172
 
 
173
        // add end of table
 
174
        int end = table->lastPosition();
 
175
        importHelper.appendFragment(end, end+1, objectIndex);
 
176
    } else {
 
177
        importHelper.appendFragments(cursor.selectionStart(), cursor.selectionEnd());
 
178
    }
 
179
}
 
180
 
 
181
void QTextDocumentFragmentPrivate::insert(QTextCursor &cursor) const
 
182
{
 
183
    if (cursor.isNull())
 
184
        return;
 
185
 
 
186
    QTextFormatCollection *formats = cursor.d->priv->formatCollection();
 
187
    QMap<int, int> formatIndexMap = fillFormatCollection(formats);
 
188
 
 
189
    QTextDocumentPrivate *destPieceTable = cursor.d->priv;
 
190
    destPieceTable->beginEditBlock();
 
191
 
 
192
    int defaultBlockFormat = formats->indexForFormat(cursor.blockFormat());
 
193
    int defaultCharFormat = formats->indexForFormat(cursor.charFormat());
 
194
 
 
195
    const bool documentWasEmpty = (destPieceTable->length() <= 1);
 
196
    bool firstFragmentWasBlock = false;
 
197
 
 
198
    for (int i = 0; i < fragments.count(); ++i) {
 
199
        const TextFragment &f = fragments.at(i);
 
200
        int blockFormatIdx = -2;
 
201
        if (f.blockFormat >= 0)
 
202
            blockFormatIdx = formatIndexMap.value(f.blockFormat, -1);
 
203
        else if (f.blockFormat == -1)
 
204
            blockFormatIdx = defaultBlockFormat;
 
205
        int formatIdx;
 
206
        if (f.charFormat != -1)
 
207
            formatIdx = formatIndexMap.value(f.charFormat, -1);
 
208
        else
 
209
            formatIdx = defaultCharFormat;
 
210
 
 
211
        if (setMarkerForHtmlExport
 
212
            && (i == 0 || i == fragments.count() - 1)) {
 
213
 
 
214
            QTextCharFormat fmt = formats->charFormat(formatIdx);
 
215
 
 
216
            int flag = 0;
 
217
            if (i == 0)
 
218
                flag |= FragmentStart;
 
219
            if (i == fragments.count() - 1)
 
220
                flag |= FragmentEnd;
 
221
 
 
222
            fmt.setProperty(QTextFormat::DocumentFragmentMark, flag);
 
223
            formatIdx = formats->indexForFormat(fmt);
 
224
        }
 
225
 
 
226
        QString text(localBuffer.constData() + f.position, f.size);
 
227
 
 
228
        if (blockFormatIdx == -2) {
 
229
            destPieceTable->insert(cursor.position(), text, formatIdx);
 
230
        } else {
 
231
            destPieceTable->insertBlock(text.at(0), cursor.position(), blockFormatIdx, formatIdx);
 
232
            if (i == 0)
 
233
                firstFragmentWasBlock = true;
 
234
        }
 
235
    }
 
236
 
 
237
    // if before the insertion the document was empty then we consider the
 
238
    // insertion as a replacement and must now also remove the initial block
 
239
    // that existed before, in case our fragment started with a block
 
240
    if (documentWasEmpty && firstFragmentWasBlock) {
 
241
        QTextCursor c = cursor;
 
242
        c.clearSelection();
 
243
        c.movePosition(QTextCursor::Start);
 
244
        c.deleteChar();
 
245
    }
 
246
 
 
247
    if (containsCompleteDocument)
 
248
        destPieceTable->rootFrame()->setFrameFormat(rootFrameFormat);
 
249
 
 
250
    // ### UNDO
 
251
    if (hasTitle)
 
252
        destPieceTable->document()->setMetaInformation(QTextDocument::DocumentTitle, title);
 
253
 
 
254
    destPieceTable->endEditBlock();
 
255
}
 
256
 
 
257
void QTextDocumentFragmentPrivate::appendText(const QString &text, int formatIdx, int blockIdx)
 
258
{
 
259
    TextFragment f;
 
260
    f.position = localBuffer.length();
 
261
    localBuffer.append(text);
 
262
    f.size = text.length();
 
263
    f.charFormat = formatIdx;
 
264
    f.blockFormat = blockIdx;
 
265
    fragments.append(f);
 
266
}
 
267
 
 
268
QMap<int, int> QTextDocumentFragmentPrivate::fillFormatCollection(QTextFormatCollection *collection) const
 
269
{
 
270
    QMap<int, int> formatIndexMap;
 
271
 
 
272
    // maps from object index used in formats to real object index
 
273
    QMap<int, int> insertedGroups;
 
274
 
 
275
    const QVector<int> &objFormats = formatCollection.objFormats;
 
276
    for (int i = 0; i < objFormats.size(); ++i) {
 
277
        int objFormat = objFormats.at(i);
 
278
        insertedGroups[i] = collection->createObjectIndex(formatCollection.format(objFormat));
 
279
    }
 
280
 
 
281
    const QVector<QTextFormat> &formats = formatCollection.formats;
 
282
    for (int i = 0; i < formats.size(); ++i) {
 
283
        QTextFormat format = formats.at(i);
 
284
 
 
285
        int objectIndex = format.objectIndex();
 
286
        if (objectIndex != -1) {
 
287
            objectIndex = insertedGroups.value(objectIndex, -1);
 
288
            format.setObjectIndex(objectIndex);
 
289
        }
 
290
 
 
291
        formatIndexMap[i] = collection->indexForFormat(format);
 
292
    }
 
293
 
 
294
    return formatIndexMap;
 
295
}
 
296
 
 
297
/*!
 
298
    \class QTextDocumentFragment qtextdocumentfragment.h
 
299
    \brief The QTextDocumentFragment class represents a piece of formatted text
 
300
    from a QTextDocument.
 
301
 
 
302
    \ingroup text
 
303
 
 
304
    A QTextDocumentFragment is a fragment of rich text, that can be inserted into
 
305
    a QTextDocument. A document fragment can be created from a
 
306
    QTextDocument, from a QTextCursor's selection, or from another
 
307
    document fragment. Document fragments can also be created by the
 
308
    static functions, fromPlainText() and fromHTML().
 
309
 
 
310
    The contents of a document fragment can be obtained as plain text
 
311
    by using the toPlainText() function, or it can be obtained as HTML
 
312
    with toHtml().
 
313
*/
 
314
 
 
315
 
 
316
/*!
 
317
    Constructs an empty QTextDocumentFragment.
 
318
 
 
319
    \sa isEmpty()
 
320
*/
 
321
QTextDocumentFragment::QTextDocumentFragment()
 
322
    : d(0)
 
323
{
 
324
}
 
325
 
 
326
/*!
 
327
    Converts the given \a document into a QTextDocumentFragment.
 
328
*/
 
329
QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
 
330
    : d(0)
 
331
{
 
332
    if (!document)
 
333
        return;
 
334
 
 
335
    QTextCursor cursor(const_cast<QTextDocument *>(document));
 
336
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
 
337
    d = new QTextDocumentFragmentPrivate(cursor);
 
338
}
 
339
 
 
340
/*!
 
341
    Creates a QTextDocumentFragment from the \a{cursor}'s selection.
 
342
    If the cursor doesn't have a selection, the created fragment is empty.
 
343
 
 
344
    \sa isEmpty() QTextCursor::selection()
 
345
*/
 
346
QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
 
347
    : d(0)
 
348
{
 
349
    if (!cursor.hasSelection())
 
350
        return;
 
351
 
 
352
    d = new QTextDocumentFragmentPrivate(cursor);
 
353
}
 
354
 
 
355
/*!
 
356
    \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
 
357
 
 
358
    Copy constructor. Creates a copy of the \a other fragment.
 
359
*/
 
360
QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
 
361
    : d(0)
 
362
{
 
363
    (*this) = rhs;
 
364
}
 
365
 
 
366
/*!
 
367
    \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
 
368
 
 
369
    Assigns the \a other fragment to this fragment.
 
370
*/
 
371
QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
 
372
{
 
373
    if (&rhs == this || (!d && !rhs.d))
 
374
        return *this;
 
375
 
 
376
    if (d && !rhs.d) {
 
377
        delete d;
 
378
        d = 0;
 
379
        return *this;
 
380
    }
 
381
 
 
382
    if (!d)
 
383
        d = new QTextDocumentFragmentPrivate;
 
384
 
 
385
    *d = *rhs.d;
 
386
 
 
387
    return *this;
 
388
}
 
389
 
 
390
/*!
 
391
    Destroys the document fragment.
 
392
*/
 
393
QTextDocumentFragment::~QTextDocumentFragment()
 
394
{
 
395
    delete d;
 
396
}
 
397
 
 
398
/*!
 
399
    Returns true if the fragment is empty; otherwise returns false.
 
400
*/
 
401
bool QTextDocumentFragment::isEmpty() const
 
402
{
 
403
    return !d || d->fragments.isEmpty();
 
404
}
 
405
 
 
406
// pull in from qtextdocument.cpp
 
407
void qt_replace_special_text_characters(QString *text);
 
408
 
 
409
/*!
 
410
    Returns the document fragment's text as plain text (i.e. with no
 
411
    formatting information).
 
412
 
 
413
    \sa toHtml()
 
414
*/
 
415
QString QTextDocumentFragment::toPlainText() const
 
416
{
 
417
    if (!d)
 
418
        return QString();
 
419
 
 
420
    QString result = d->localBuffer;
 
421
 
 
422
    // if we have a complete document that contains the initial paragraph
 
423
    // at the beginning then we don't want to see that one in the plaintext
 
424
    // output, as otherwise all plaintext output would always start with a
 
425
    // newline
 
426
    if (d->containsCompleteDocument
 
427
        && !result.isEmpty()
 
428
        && result.at(0) == QChar::ParagraphSeparator)
 
429
        result.remove(0, 1);
 
430
 
 
431
    qt_replace_special_text_characters(&result);
 
432
    return result;
 
433
}
 
434
 
 
435
/*!
 
436
    Returns the contents of the document fragment as HTML.
 
437
 
 
438
    \sa toPlainText()
 
439
*/
 
440
QString QTextDocumentFragment::toHtml() const
 
441
{
 
442
    if (!d)
 
443
        return QString();
 
444
 
 
445
    QTextDocument doc;
 
446
    QTextCursor cursor(&doc);
 
447
 
 
448
    d->setMarkerForHtmlExport = (d->containsCompleteDocument == false);
 
449
    cursor.insertFragment(*this);
 
450
    d->setMarkerForHtmlExport = false;
 
451
    return doc.toHtml();
 
452
}
 
453
 
 
454
/*!
 
455
    Returns a document fragment that contains the given \a plainText.
 
456
 
 
457
    When inserting such a fragment into a QTextDocument the current char format of
 
458
    the QTextCursor used for insertion is used as format for the text.
 
459
*/
 
460
QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
 
461
{
 
462
    QTextDocumentFragment res;
 
463
 
 
464
    res.d = new QTextDocumentFragmentPrivate;
 
465
 
 
466
    bool seenCRLF = false;
 
467
 
 
468
    int textStart = 0;
 
469
    for (int i = 0; i < plainText.length(); ++i) {
 
470
        QChar ch = plainText.at(i);
 
471
        if (ch == QLatin1Char('\n')
 
472
            || ch == QChar::ParagraphSeparator) {
 
473
 
 
474
            const int textEnd = (seenCRLF ? i - 1 : i);
 
475
 
 
476
            if (textEnd > textStart)
 
477
                res.d->appendText(QString::fromRawData(plainText.unicode() + textStart, textEnd - textStart), -1);
 
478
 
 
479
            textStart = i + 1;
 
480
            res.d->appendText(QString(QChar::ParagraphSeparator), -1, -1);
 
481
 
 
482
            seenCRLF = false;
 
483
        } else if (ch == QLatin1Char('\r')
 
484
                   && (i + 1) < plainText.length()
 
485
                   && plainText.at(i + 1) == QLatin1Char('\n')) {
 
486
            seenCRLF = true;
 
487
        }
 
488
    }
 
489
    if (textStart < plainText.length())
 
490
        res.d->appendText(QString::fromRawData(plainText.unicode() + textStart, plainText.length() - textStart), -1);
 
491
 
 
492
    return res;
 
493
}
 
494
 
 
495
QTextHTMLImporter::QTextHTMLImporter(QTextDocumentFragmentPrivate *_d, const QString &html)
 
496
    : d(_d), indent(0), setNamedAnchorInNextOutput(false)
 
497
{
 
498
    parse(html);
 
499
//    dumpHtml();
 
500
}
 
501
 
 
502
static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
 
503
{
 
504
    if (style == QTextListFormat::ListDisc)
 
505
        return QTextListFormat::ListCircle;
 
506
    else if (style == QTextListFormat::ListCircle)
 
507
        return QTextListFormat::ListSquare;
 
508
    return style;
 
509
}
 
510
 
 
511
void QTextHTMLImporter::import()
 
512
{
 
513
    bool hasBlock = false;
 
514
    bool forceBlockMerging = false;
 
515
    for (int i = 0; i < count(); ++i) {
 
516
        const QTextHtmlParserNode *node = &at(i);
 
517
 
 
518
        /*
 
519
         * process each node in three stages:
 
520
         * 1) check if the hierarchy changed and we therefore passed the
 
521
         *    equivalent of a closing tag -> we may need to finish off
 
522
         *    some structures like tables
 
523
         *
 
524
         * 2) check if the current node is a special node like a
 
525
         *    <table>, <ul> or <img> tag that requires special processing
 
526
         *
 
527
         * 3) if the node should result in a QTextBlock create one and
 
528
         *    finally insert text that may be attached to the node
 
529
         */
 
530
 
 
531
        /* emit 'closing' table blocks or adjust current indent level
 
532
         * if we
 
533
         *  1) are beyond the first node
 
534
         *  2) the current node not being a child of the previous node
 
535
         *      means there was a tag closing in the input html
 
536
         */
 
537
        if (i > 0 && (node->parent != i - 1)) {
 
538
            const bool blockTagClosed = closeTag(i);
 
539
            if (hasBlock && blockTagClosed)
 
540
                hasBlock = false;
 
541
 
 
542
            // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
 
543
            if (blockTagClosed
 
544
                && !hasBlock
 
545
                && !node->isBlock
 
546
                && !node->text.isEmpty()
 
547
                && node->displayMode != QTextHtmlElement::DisplayNone) {
 
548
 
 
549
                QTextBlockFormat block = node->blockFormat();
 
550
                block.setIndent(indent);
 
551
 
 
552
                appendBlock(block, node->charFormat());
 
553
 
 
554
                hasBlock = true;
 
555
            }
 
556
        }
 
557
 
 
558
        if (node->displayMode == QTextHtmlElement::DisplayNone) {
 
559
            if (node->id == Html_title) {
 
560
                d->hasTitle = true;
 
561
                d->title = node->text;
 
562
            }
 
563
            // ignore explicitly 'invisible' elements
 
564
            continue;
 
565
        } else if (node->id == Html_body) {
 
566
            d->containsCompleteDocument = true;
 
567
            if (node->bgColor.isValid()) {
 
568
                d->rootFrameFormat.setBackground(QBrush(node->bgColor));
 
569
                const_cast<QTextHtmlParserNode *>(node)->bgColor = QColor();
 
570
            }
 
571
        } else if (node->isListStart) {
 
572
 
 
573
            QTextListFormat::Style style = node->listStyle;
 
574
 
 
575
            if (node->id == Html_ul && !node->hasOwnListStyle && node->parent) {
 
576
                const QTextHtmlParserNode *n = &at(node->parent);
 
577
                while (n) {
 
578
                    if (n->id == Html_ul) {
 
579
                        style = nextListStyle(node->listStyle);
 
580
                    }
 
581
                    if (n->parent)
 
582
                        n = &at(n->parent);
 
583
                    else
 
584
                        n = 0;
 
585
                }
 
586
            }
 
587
 
 
588
            QTextListFormat listFmt;
 
589
            listFmt.setStyle(style);
 
590
 
 
591
            ++indent;
 
592
            if (node->hasCssListIndent)
 
593
                listFmt.setIndent(node->cssListIndent);
 
594
            else
 
595
                listFmt.setIndent(indent);
 
596
 
 
597
            listReferences.append(d->formatCollection.createObjectIndex(listFmt));
 
598
 
 
599
            if (node->text.isEmpty())
 
600
                continue;
 
601
        } else if (node->id == Html_table) {
 
602
            Table t;
 
603
            if (scanTable(i, &t)) {
 
604
                tables.append(t);
 
605
                hasBlock = false;
 
606
            }
 
607
            continue;
 
608
        } else if (node->id == Html_tr && !tables.isEmpty()) {
 
609
            tables[tables.size() - 1].currentRow++;
 
610
            continue;
 
611
        } else if (node->id == Html_img) {
 
612
            QTextImageFormat fmt;
 
613
            fmt.setName(node->imageName);
 
614
 
 
615
            if (node->imageWidth >= 0)
 
616
                fmt.setWidth(node->imageWidth);
 
617
            if (node->imageHeight >= 0)
 
618
                fmt.setHeight(node->imageHeight);
 
619
            QTextFrameFormat::Position f = QTextFrameFormat::Position(node->cssFloat);
 
620
            QTextFrameFormat ffmt;
 
621
            ffmt.setPosition(f);
 
622
            int objIndex = d->formatCollection.createObjectIndex(ffmt);
 
623
            fmt.setObjectIndex(objIndex);
 
624
 
 
625
            appendImage(fmt);
 
626
            hasBlock = false;
 
627
            continue;
 
628
        }
 
629
 
 
630
        if (node->isBlock) {
 
631
            QTextBlockFormat block;
 
632
            QTextCharFormat charFmt;
 
633
 
 
634
            QChar separator = QChar::ParagraphSeparator;
 
635
 
 
636
            if (hasBlock) {
 
637
                Q_ASSERT(d->fragments.last().blockFormat >= 0);
 
638
                block = d->formatCollection.blockFormat(d->fragments.last().blockFormat);
 
639
                charFmt = d->formatCollection.charFormat(d->fragments.last().charFormat);
 
640
            }
 
641
 
 
642
            // collapse
 
643
            block.setTopMargin(qMax(block.topMargin(), (qreal)topMargin(i)));
 
644
 
 
645
            int bottomMargin = this->bottomMargin(i);
 
646
 
 
647
            // for list items we may want to collapse with the bottom margin of the
 
648
            // list.
 
649
            if (node->isListItem) {
 
650
                if (node->parent && at(node->parent).isListStart) {
 
651
                    const int listId = node->parent;
 
652
                    const QTextHtmlParserNode *list = &at(listId);
 
653
                    if (list->children.last() == i /* == index of node */)
 
654
                        bottomMargin = qMax(bottomMargin, this->bottomMargin(listId));
 
655
                }
 
656
            }
 
657
 
 
658
            block.setBottomMargin(bottomMargin);
 
659
 
 
660
            block.setLeftMargin(leftMargin(i));
 
661
            block.setRightMargin(rightMargin(i));
 
662
 
 
663
            if (node->isListItem) {
 
664
                if (!listReferences.isEmpty()) {
 
665
                    block.setObjectIndex(listReferences.last());
 
666
                } else {
 
667
//                    qWarning("QTextDocumentFragment(html import): list item outside list found. bad html?");
 
668
                }
 
669
            } else if (indent && block.objectIndex() != listReferences.last()) {
 
670
                block.setIndent(indent);
 
671
            }
 
672
 
 
673
            block.merge(node->blockFormat());
 
674
            charFmt.merge(node->charFormat());
 
675
 
 
676
            if (node->isTableCell && !tables.isEmpty()) {
 
677
 
 
678
                charFmt.setObjectIndex(tables[tables.size() - 1].tableIndex);
 
679
 
 
680
                if (node->bgColor.isValid())
 
681
                    charFmt.setBackground(QBrush(node->bgColor));
 
682
 
 
683
                charFmt.setTableCellColumnSpan(node->tableCellColSpan);
 
684
                charFmt.setTableCellRowSpan(node->tableCellRowSpan);
 
685
 
 
686
                separator = QTextBeginningOfFrame;
 
687
 
 
688
                tables[tables.size() - 1].currentColumnCount += node->tableCellColSpan;
 
689
 
 
690
                hasBlock = false;
 
691
            }
 
692
 
 
693
            // ####################
 
694
//                block.setFloatPosition(node->cssFloat);
 
695
 
 
696
            if (node->wsm == QTextHtmlParserNode::WhiteSpacePre)
 
697
                block.setNonBreakableLines(true);
 
698
 
 
699
            if (node->bgColor.isValid())
 
700
                block.setBackground(QBrush(node->bgColor));
 
701
 
 
702
            if (hasBlock && (!node->isEmptyParagraph || forceBlockMerging)) {
 
703
                d->fragments.last().blockFormat = d->formatCollection.indexForFormat(block);
 
704
                d->fragments.last().charFormat = d->formatCollection.indexForFormat(charFmt);
 
705
            } else {
 
706
                appendBlock(block, charFmt, separator);
 
707
            }
 
708
 
 
709
            forceBlockMerging = false;
 
710
            if (node->id == Html_body || node->id == Html_html)
 
711
                forceBlockMerging = true;
 
712
 
 
713
            if (node->isEmptyParagraph)
 
714
                continue;
 
715
 
 
716
            hasBlock = true;
 
717
        } 
 
718
 
 
719
        if (node->isAnchor && !node->anchorName.isEmpty()) {
 
720
            setNamedAnchorInNextOutput = true;
 
721
            namedAnchor = node->anchorName;
 
722
        }
 
723
        if (node->text.size() == 0)
 
724
            continue;
 
725
        hasBlock = false;
 
726
 
 
727
        appendText(node->text, node->charFormat());
 
728
    }
 
729
 
 
730
    if (listReferences.size() || tables.size())
 
731
        closeTag(count() - 1);
 
732
 
 
733
}
 
734
 
 
735
// returns true if a block tag was closed
 
736
bool QTextHTMLImporter::closeTag(int i)
 
737
{
 
738
    const bool atLastNode = (i == count() - 1);
 
739
    const QTextHtmlParserNode *closedNode = &at(i - 1);
 
740
    const int endDepth = atLastNode ? - 1 : depth(i) - 1;
 
741
    int depth = this->depth(i - 1);
 
742
    bool blockTagClosed = false;
 
743
 
 
744
    while (depth > endDepth) {
 
745
        if (closedNode->id == Html_tr && !tables.isEmpty()) {
 
746
            Table &t = tables[tables.size() -1];
 
747
 
 
748
            QTextCharFormat charFmt;
 
749
            charFmt.setObjectIndex(t.tableIndex);
 
750
 
 
751
            const int rowSpanCells = t.rowSpanCellsPerRow.value(t.currentRow, 0);
 
752
 
 
753
            while (t.currentColumnCount < t.columns - rowSpanCells) {
 
754
                appendBlock(QTextBlockFormat(), charFmt, QTextBeginningOfFrame);
 
755
                ++t.currentColumnCount;
 
756
            }
 
757
 
 
758
            t.currentColumnCount = 0;
 
759
            blockTagClosed = true;
 
760
        } else if (closedNode->id == Html_table && !tables.isEmpty()) {
 
761
            QTextCharFormat charFmt;
 
762
            charFmt.setObjectIndex(tables[tables.size() - 1].tableIndex);
 
763
            QTextBlockFormat fmt;
 
764
            appendBlock(fmt, charFmt, QTextEndOfFrame);
 
765
            tables.resize(tables.size() - 1);
 
766
            // we don't need an extra block after tables, so we don't
 
767
            // claim to have closed one for the creation of a new one
 
768
            // in import()
 
769
            blockTagClosed = false;
 
770
        } else if (closedNode->isListStart) {
 
771
 
 
772
            Q_ASSERT(!listReferences.isEmpty());
 
773
 
 
774
            listReferences.resize(listReferences.size() - 1);
 
775
            --indent;
 
776
            blockTagClosed = true;
 
777
        }
 
778
 
 
779
        closedNode = &at(closedNode->parent);
 
780
        --depth;
 
781
    }
 
782
 
 
783
    return blockTagClosed;
 
784
}
 
785
 
 
786
bool QTextHTMLImporter::scanTable(int tableNodeIdx, Table *table)
 
787
{
 
788
    table->columns = 0;
 
789
 
 
790
    QVector<QTextLength> columnWidths;
 
791
 
 
792
    int cellCount = 0;
 
793
    bool inFirstRow = true;
 
794
    int effectiveRow = 0;
 
795
    foreach (int row, at(tableNodeIdx).children) {
 
796
        if (at(row).id == Html_tr) {
 
797
            int colsInRow = 0;
 
798
 
 
799
            foreach (int cell, at(row).children)
 
800
                if (at(cell).isTableCell) {
 
801
                    ++cellCount;
 
802
 
 
803
                    const QTextHtmlParserNode &c = at(cell);
 
804
                    colsInRow += c.tableCellColSpan;
 
805
 
 
806
                    if (c.tableCellRowSpan > 1) {
 
807
                        table->rowSpanCellsPerRow.resize(effectiveRow + c.tableCellRowSpan + 1);
 
808
 
 
809
                        for (int r = effectiveRow + 1; r < effectiveRow + c.tableCellRowSpan; ++r)
 
810
                            table->rowSpanCellsPerRow[r]++;
 
811
                    }
 
812
 
 
813
                    if (inFirstRow || colsInRow > columnWidths.count()) {
 
814
                        while (columnWidths.count() < colsInRow)
 
815
                            columnWidths << c.width;
 
816
                    }
 
817
                }
 
818
 
 
819
            table->columns = qMax(table->columns, colsInRow);
 
820
            inFirstRow = false;
 
821
 
 
822
            ++effectiveRow;
 
823
        }
 
824
    }
 
825
 
 
826
    if (cellCount == 0)
 
827
        return false;
 
828
 
 
829
    const QTextHtmlParserNode &node = at(tableNodeIdx);
 
830
    QTextTableFormat fmt;
 
831
    fmt.setBorder(node.tableBorder);
 
832
    fmt.setWidth(node.width);
 
833
    fmt.setCellSpacing(node.tableCellSpacing);
 
834
    fmt.setCellPadding(node.tableCellPadding);
 
835
    if (node.alignment)
 
836
        fmt.setAlignment(node.alignment);
 
837
    if (node.direction < 2)
 
838
        fmt.setLayoutDirection(Qt::LayoutDirection(node.direction));
 
839
    if (node.bgColor.isValid())
 
840
        fmt.setBackground(QBrush(node.bgColor));
 
841
    else
 
842
        fmt.clearBackground();
 
843
    fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
 
844
 
 
845
    fmt.setColumns(table->columns);
 
846
    fmt.setColumnWidthConstraints(columnWidths);
 
847
    table->tableIndex = d->formatCollection.createObjectIndex(fmt);
 
848
    return true;
 
849
}
 
850
 
 
851
void QTextHTMLImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt, const QChar &separator)
 
852
{
 
853
    if (setNamedAnchorInNextOutput) {
 
854
        charFmt.setAnchor(true);
 
855
        charFmt.setAnchorName(namedAnchor);
 
856
        setNamedAnchorInNextOutput = false;
 
857
    }
 
858
 
 
859
    d->appendText(QString(separator), d->formatCollection.indexForFormat(charFmt), d->formatCollection.indexForFormat(format));
 
860
}
 
861
 
 
862
void QTextHTMLImporter::appendText(QString text, QTextCharFormat format)
 
863
{
 
864
    if (setNamedAnchorInNextOutput && !text.isEmpty()) {
 
865
        QTextCharFormat fmt = format;
 
866
        fmt.setAnchor(true);
 
867
        fmt.setAnchorName(namedAnchor);
 
868
        d->appendText(QString(text.at(0)), d->formatCollection.indexForFormat(fmt));
 
869
 
 
870
        text.remove(0, 1);
 
871
        format.setAnchor(false);
 
872
        format.setAnchorName(QString());
 
873
 
 
874
        setNamedAnchorInNextOutput = false;
 
875
    }
 
876
 
 
877
    if (!text.isEmpty()) {
 
878
        d->appendText(text, d->formatCollection.indexForFormat(format));
 
879
    }
 
880
}
 
881
 
 
882
/*!
 
883
    \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
 
884
 
 
885
    Returns a QTextDocumentFragment based on the arbitrary piece of
 
886
    HTML in the given \a text. The formatting is preserved as much as
 
887
    possible; for example, "<b>bold</b>" will become a document
 
888
    fragment with the text "bold" with a bold character format.
 
889
*/
 
890
QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &_html)
 
891
{
 
892
    QTextDocumentFragment res;
 
893
    res.d = new QTextDocumentFragmentPrivate;
 
894
 
 
895
    QString html = _html;
 
896
 
 
897
    const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
 
898
    if (startFragmentPos != -1) {
 
899
        const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
 
900
        if (startFragmentPos < endFragmentPos)
 
901
            html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
 
902
        else
 
903
            html = html.mid(startFragmentPos);
 
904
 
 
905
        html.prepend(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
 
906
 
 
907
        res.d->containsCompleteDocument = false;
 
908
    } else {
 
909
        res.d->containsCompleteDocument = true;
 
910
    }
 
911
 
 
912
    QTextHTMLImporter(res.d, html).import();
 
913
    return res;
 
914
}