1 /****************************************************************************
3 ** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
6 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** Licensees holding valid Qt Commercial licenses may use this file in
10 ** accordance with the Qt Commercial License Agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and Nokia.
15 ** GNU General Public License Usage
16 ** Alternatively, this file may be used under the terms of the GNU
17 ** General Public License versions 2.0 or 3.0 as published by the Free
18 ** Software Foundation and appearing in the file LICENSE.GPL included in
19 ** the packaging of this file. Please review the following information
20 ** to ensure GNU General Public Licensing requirements will be met:
21 ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
22 ** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
23 ** exception, Nokia gives you certain additional rights. These rights
24 ** are described in the Nokia Qt GPL Exception version 1.3, included in
25 ** the file GPL_EXCEPTION.txt in this package.
27 ** Qt for Windows(R) Licensees
28 ** As a special exception, Nokia, as the sole copyright holder for Qt
29 ** Designer, grants users of the Qt/Eclipse Integration plug-in the
30 ** right for the Qt/Eclipse Integration to link to functionality
31 ** provided by Qt Designer and its related libraries.
33 ** If you are unsure which license is appropriate for your use, please
34 ** contact the sales department at qt-sales@nokia.com.
36 ****************************************************************************/
38 #include "qtextdocumentfragment.h"
39 #include "qtextdocumentfragment_p.h"
40 #include "qtextcursor_p.h"
41 #include "qtextlist.h"
42 #include "private/qunicodetables_p.h"
45 #include <qtextcodec.h>
46 #include <qbytearray.h>
47 #include <qdatastream.h>
48 #include <qdatetime.h>
52 QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
53 : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
55 src = _source.d->priv;
56 dst = _destination.d->priv;
57 insertPos = _destination.position();
58 this->forceCharFormat = forceCharFormat;
59 primaryCharFormatIndex = convertFormatIndex(fmt);
63 int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
65 QTextFormat fmt = oldFormat;
66 if (objectIndexToSet != -1) {
67 fmt.setObjectIndex(objectIndexToSet);
68 } else if (fmt.objectIndex() != -1) {
69 int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
70 if (newObjectIndex == -1) {
71 QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex());
72 Q_ASSERT(objFormat.objectIndex() == -1);
73 newObjectIndex = formatCollection.createObjectIndex(objFormat);
74 objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
76 fmt.setObjectIndex(newObjectIndex);
78 int idx = formatCollection.indexForFormat(fmt);
79 Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type());
83 int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex)
85 QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos);
86 const QTextFragmentData * const frag = fragIt.value();
88 Q_ASSERT(objectIndex == -1
89 || (frag->size == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1));
93 charFormatIndex = primaryCharFormatIndex;
95 charFormatIndex = convertFormatIndex(frag->format, objectIndex);
97 const int inFragmentOffset = qMax(0, pos - fragIt.position());
98 int charsToCopy = qMin(int(frag->size - inFragmentOffset), endPos - pos);
100 QTextBlock nextBlock = src->blocksFind(pos + 1);
103 if (nextBlock.position() == pos + 1) {
104 blockIdx = convertFormatIndex(nextBlock.blockFormat());
105 } else if (pos == 0 && insertPos == 0) {
106 dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat());
107 dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat());
110 QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy);
111 if (txtToInsert.length() == 1
112 && (txtToInsert.at(0) == QChar::ParagraphSeparator
113 || txtToInsert.at(0) == QTextBeginningOfFrame
114 || txtToInsert.at(0) == QTextEndOfFrame
117 dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex);
120 if (nextBlock.textList()) {
121 QTextBlock dstBlock = dst->blocksFind(insertPos);
122 if (!dstBlock.textList()) {
123 blockIdx = convertFormatIndex(nextBlock.blockFormat());
124 dst->insertBlock(insertPos, blockIdx, charFormatIndex);
128 dst->insert(insertPos, txtToInsert, charFormatIndex);
129 const int userState = nextBlock.userState();
131 dst->blocksFind(insertPos).setUserState(userState);
132 insertPos += txtToInsert.length();
138 void QTextCopyHelper::appendFragments(int pos, int endPos)
140 Q_ASSERT(pos < endPos);
143 pos += appendFragment(pos, endPos);
146 void QTextCopyHelper::copy()
148 if (cursor.hasComplexSelection()) {
149 QTextTable *table = cursor.currentTable();
150 int row_start, col_start, num_rows, num_cols;
151 cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
153 QTextTableFormat tableFormat = table->format();
154 tableFormat.setColumns(num_cols);
155 tableFormat.clearColumnWidthConstraints();
156 const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat);
158 Q_ASSERT(row_start != -1);
159 for (int r = row_start; r < row_start + num_rows; ++r) {
160 for (int c = col_start; c < col_start + num_cols; ++c) {
161 QTextTableCell cell = table->cellAt(r, c);
162 const int rspan = cell.rowSpan();
163 const int cspan = cell.columnSpan();
170 int cc = cell.column();
175 // add the QTextBeginningOfFrame
176 QTextCharFormat cellFormat = cell.format();
177 if (r + rspan >= row_start + num_rows) {
178 cellFormat.setTableCellRowSpan(row_start + num_rows - r);
180 if (c + cspan >= col_start + num_cols) {
181 cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
183 const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex);
186 const int cellPos = cell.firstPosition();
187 QTextBlock block = src->blocksFind(cellPos);
188 if (block.position() == cellPos) {
189 blockIdx = convertFormatIndex(block.blockFormat());
192 dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex);
195 // nothing to add for empty cells
196 if (cell.lastPosition() > cellPos) {
198 appendFragments(cellPos, cell.lastPosition());
204 int end = table->lastPosition();
205 appendFragment(end, end+1, objectIndex);
207 appendFragments(cursor.selectionStart(), cursor.selectionEnd());
211 QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor)
212 : ref(1), doc(new QTextDocument), importedFromPlainText(false)
214 doc->setUndoRedoEnabled(false);
216 if (!_cursor.hasSelection())
219 doc->docHandle()->beginEditBlock();
220 QTextCursor destCursor(doc);
221 QTextCopyHelper(_cursor, destCursor).copy();
222 doc->docHandle()->endEditBlock();
225 doc->docHandle()->mergeCachedResources(_cursor.d->priv);
228 void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const
230 if (_cursor.isNull())
233 QTextDocumentPrivate *destPieceTable = _cursor.d->priv;
234 destPieceTable->beginEditBlock();
236 QTextCursor sourceCursor(doc);
237 sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
238 QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy();
240 destPieceTable->endEditBlock();
244 \class QTextDocumentFragment
247 \brief The QTextDocumentFragment class represents a piece of formatted text
248 from a QTextDocument.
253 A QTextDocumentFragment is a fragment of rich text, that can be inserted into
254 a QTextDocument. A document fragment can be created from a
255 QTextDocument, from a QTextCursor's selection, or from another
256 document fragment. Document fragments can also be created by the
257 static functions, fromPlainText() and fromHtml().
259 The contents of a document fragment can be obtained as plain text
260 by using the toPlainText() function, or it can be obtained as HTML
266 Constructs an empty QTextDocumentFragment.
270 QTextDocumentFragment::QTextDocumentFragment()
276 Converts the given \a document into a QTextDocumentFragment.
277 Note that the QTextDocumentFragment only stores the document contents, not meta information
278 like the document's title.
280 QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
286 QTextCursor cursor(const_cast<QTextDocument *>(document));
287 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
288 d = new QTextDocumentFragmentPrivate(cursor);
292 Creates a QTextDocumentFragment from the \a{cursor}'s selection.
293 If the cursor doesn't have a selection, the created fragment is empty.
295 \sa isEmpty() QTextCursor::selection()
297 QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
300 if (!cursor.hasSelection())
303 d = new QTextDocumentFragmentPrivate(cursor);
307 \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
309 Copy constructor. Creates a copy of the \a other fragment.
311 QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
319 \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
321 Assigns the \a other fragment to this fragment.
323 QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
327 if (d && !d->ref.deref())
334 Destroys the document fragment.
336 QTextDocumentFragment::~QTextDocumentFragment()
338 if (d && !d->ref.deref())
343 Returns true if the fragment is empty; otherwise returns false.
345 bool QTextDocumentFragment::isEmpty() const
347 return !d || !d->doc || d->doc->docHandle()->length() <= 1;
351 Returns the document fragment's text as plain text (i.e. with no
352 formatting information).
356 QString QTextDocumentFragment::toPlainText() const
361 return d->doc->toPlainText();
364 // #### Qt 5: merge with other overload
369 #ifndef QT_NO_TEXTHTMLPARSER
371 QString QTextDocumentFragment::toHtml() const
373 return toHtml(QByteArray());
379 Returns the contents of the document fragment as HTML,
380 using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
382 \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
384 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
389 return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
392 #endif // QT_NO_TEXTHTMLPARSER
395 Returns a document fragment that contains the given \a plainText.
397 When inserting such a fragment into a QTextDocument the current char format of
398 the QTextCursor used for insertion is used as format for the text.
400 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
402 QTextDocumentFragment res;
404 res.d = new QTextDocumentFragmentPrivate;
405 res.d->importedFromPlainText = true;
406 QTextCursor cursor(res.d->doc);
407 cursor.insertText(plainText);
411 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
413 if (style == QTextListFormat::ListDisc)
414 return QTextListFormat::ListCircle;
415 else if (style == QTextListFormat::ListCircle)
416 return QTextListFormat::ListSquare;
420 #ifndef QT_NO_TEXTHTMLPARSER
422 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
423 : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
425 cursor = QTextCursor(doc);
426 wsm = QTextHtmlParserNode::WhiteSpaceNormal;
428 QString html = _html;
429 const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
430 if (startFragmentPos != -1) {
431 const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
432 if (startFragmentPos < endFragmentPos)
433 html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
435 html = html.mid(startFragmentPos);
437 html.prepend(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
440 parse(html, resourceProvider ? resourceProvider : doc);
444 void QTextHtmlImporter::import()
446 cursor.beginEditBlock();
448 forceBlockMerging = false;
449 compressNextWhitespace = RemoveWhiteSpace;
450 blockTagClosed = false;
451 for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
452 currentNode = &at(currentNodeIdx);
453 wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
456 * process each node in three stages:
457 * 1) check if the hierarchy changed and we therefore passed the
458 * equivalent of a closing tag -> we may need to finish off
459 * some structures like tables
461 * 2) check if the current node is a special node like a
462 * <table>, <ul> or <img> tag that requires special processing
464 * 3) if the node should result in a QTextBlock create one and
465 * finally insert text that may be attached to the node
468 /* emit 'closing' table blocks or adjust current indent level
470 * 1) are beyond the first node
471 * 2) the current node not being a child of the previous node
472 * means there was a tag closing in the input html
474 if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
475 blockTagClosed = closeTag();
476 // visually collapse subsequent block tags, but if the element after the closed block tag
477 // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
478 // hasBlock to false.
480 && !currentNode->isBlock()
481 && currentNode->id != Html_unknown)
484 } else if (hasBlock) {
485 // when collapsing subsequent block tags we need to clear the block format
486 QTextBlockFormat blockFormat = currentNode->blockFormat;
487 blockFormat.setIndent(indent);
489 QTextBlockFormat oldFormat = cursor.blockFormat();
490 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
491 QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
492 if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
493 /* We remove an empty paragrah that requested a page break after.
494 moving that request to the next paragraph means we also need to make
495 that a pagebreak before to keep the same visual appearance.
497 pageBreak = QTextFormat::PageBreak_AlwaysBefore;
498 blockFormat.setPageBreakPolicy(pageBreak);
501 cursor.setBlockFormat(blockFormat);
505 if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
506 if (currentNode->id == Html_title)
507 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
508 // ignore explicitly 'invisible' elements
512 if (processSpecialNodes() == ContinueWithNextNode)
515 // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
518 && !currentNode->isBlock()
519 && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
520 && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
522 QTextBlockFormat block = currentNode->blockFormat;
523 block.setIndent(indent);
525 appendBlock(block, currentNode->charFormat);
530 if (currentNode->isBlock()) {
531 if (processBlockNode() == ContinueWithNextNode)
535 if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
536 namedAnchors.append(currentNode->charFormat.anchorName());
539 if (appendNodeText())
540 hasBlock = false; // if we actually appended text then we don't
541 // have an empty block anymore
544 cursor.endEditBlock();
547 bool QTextHtmlImporter::appendNodeText()
549 const int initialCursorPosition = cursor.position();
550 QTextCharFormat format = currentNode->charFormat;
552 if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
553 compressNextWhitespace = PreserveWhiteSpace;
555 QString text = currentNode->text;
557 QString textToInsert;
558 textToInsert.reserve(text.size());
560 for (int i = 0; i < text.length(); ++i) {
561 QChar ch = text.at(i);
565 && ch != QChar::ParagraphSeparator) {
567 if (compressNextWhitespace == CollapseWhiteSpace)
568 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
569 else if(compressNextWhitespace == RemoveWhiteSpace)
572 if (wsm == QTextHtmlParserNode::WhiteSpacePre
575 if (ch == QLatin1Char('\n')) {
578 } else if (ch == QLatin1Char('\r')) {
581 } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
582 compressNextWhitespace = RemoveWhiteSpace;
583 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
586 ch = QLatin1Char(' ');
589 compressNextWhitespace = PreserveWhiteSpace;
592 if (ch == QLatin1Char('\n')
593 || ch == QChar::ParagraphSeparator) {
595 if (!textToInsert.isEmpty()) {
596 cursor.insertText(textToInsert, format);
597 textToInsert.clear();
600 QTextBlockFormat fmt = cursor.blockFormat();
602 if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
603 QTextBlockFormat tmp = fmt;
604 tmp.clearProperty(QTextFormat::BlockBottomMargin);
605 cursor.setBlockFormat(tmp);
608 fmt.clearProperty(QTextFormat::BlockTopMargin);
609 appendBlock(fmt, cursor.charFormat());
611 if (!namedAnchors.isEmpty()) {
612 if (!textToInsert.isEmpty()) {
613 cursor.insertText(textToInsert, format);
614 textToInsert.clear();
617 format.setAnchor(true);
618 format.setAnchorNames(namedAnchors);
619 cursor.insertText(ch, format);
620 namedAnchors.clear();
621 format.clearProperty(QTextFormat::IsAnchor);
622 format.clearProperty(QTextFormat::AnchorName);
629 if (!textToInsert.isEmpty()) {
630 cursor.insertText(textToInsert, format);
633 return cursor.position() != initialCursorPosition;
636 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
638 switch (currentNode->id) {
640 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
641 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
642 fmt.setBackground(currentNode->charFormat.background());
643 doc->rootFrame()->setFrameFormat(fmt);
644 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
646 compressNextWhitespace = RemoveWhiteSpace;
651 QTextListFormat::Style style = currentNode->listStyle;
653 if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
654 const QTextHtmlParserNode *n = &at(currentNode->parent);
656 if (n->id == Html_ul) {
657 style = nextListStyle(currentNode->listStyle);
666 QTextListFormat listFmt;
667 listFmt.setStyle(style);
670 if (currentNode->hasCssListIndent)
671 listFmt.setIndent(currentNode->cssListIndent);
673 listFmt.setIndent(indent);
677 l.listNode = currentNodeIdx;
679 compressNextWhitespace = RemoveWhiteSpace;
681 // broken html: <ul>Text here<li>Foo
682 const QString simpl = currentNode->text.simplified();
683 if (simpl.isEmpty() || simpl.at(0).isSpace())
684 return ContinueWithNextNode;
689 Table t = scanTable(currentNodeIdx);
692 compressNextWhitespace = RemoveWhiteSpace;
693 return ContinueWithNextNode;
697 return ContinueWithNextNode;
700 QTextImageFormat fmt;
701 fmt.setName(currentNode->imageName);
703 fmt.merge(currentNode->charFormat);
705 if (currentNode->imageWidth >= 0)
706 fmt.setWidth(currentNode->imageWidth);
707 if (currentNode->imageHeight >= 0)
708 fmt.setHeight(currentNode->imageHeight);
710 cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
712 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
713 cursor.mergeCharFormat(currentNode->charFormat);
714 cursor.movePosition(QTextCursor::Right);
715 compressNextWhitespace = CollapseWhiteSpace;
718 return ContinueWithNextNode;
722 QTextBlockFormat blockFormat = currentNode->blockFormat;
723 blockFormat.setTopMargin(topMargin(currentNodeIdx));
724 blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
725 blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
726 if (hasBlock && importMode == ImportToDocument)
727 cursor.mergeBlockFormat(blockFormat);
729 appendBlock(blockFormat);
731 compressNextWhitespace = RemoveWhiteSpace;
732 return ContinueWithNextNode;
737 return ContinueWithCurrentNode;
740 // returns true if a block tag was closed
741 bool QTextHtmlImporter::closeTag()
743 const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
744 const int endDepth = depth(currentNodeIdx) - 1;
745 int depth = this->depth(currentNodeIdx - 1);
746 bool blockTagClosed = false;
748 while (depth > endDepth) {
750 if (!tables.isEmpty())
753 switch (closedNode->id) {
755 if (t && !t->isTextFrame) {
758 // for broken html with rowspans but missing tr tags
759 while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
763 blockTagClosed = true;
769 indent = t->lastIndent;
771 tables.resize(tables.size() - 1);
774 if (tables.isEmpty()) {
775 cursor = doc->rootFrame()->lastCursorPosition();
779 cursor = t->frame->lastCursorPosition();
780 else if (!t->currentCell.atEnd())
781 cursor = t->currentCell.cell().lastCursorPosition();
784 // we don't need an extra block after tables, so we don't
785 // claim to have closed one for the creation of a new one
787 blockTagClosed = false;
788 compressNextWhitespace = RemoveWhiteSpace;
793 if (t && !t->isTextFrame)
795 blockTagClosed = true;
796 compressNextWhitespace = RemoveWhiteSpace;
803 lists.resize(lists.size() - 1);
805 blockTagClosed = true;
809 compressNextWhitespace = RemoveWhiteSpace;
813 if (closedNode->children.isEmpty())
817 if (closedNode->isBlock())
818 blockTagClosed = true;
822 closedNode = &at(closedNode->parent);
826 return blockTagClosed;
829 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
834 QVector<QTextLength> columnWidths;
836 int tableHeaderRowCount = 0;
837 QVector<int> rowNodes;
838 rowNodes.reserve(at(tableNodeIdx).children.count());
839 foreach (int row, at(tableNodeIdx).children)
840 switch (at(row).id) {
847 foreach (int potentialRow, at(row).children)
848 if (at(potentialRow).id == Html_tr) {
849 rowNodes += potentialRow;
850 if (at(row).id == Html_thead)
851 ++tableHeaderRowCount;
857 QVector<RowColSpanInfo> rowColSpans;
858 QVector<RowColSpanInfo> rowColSpanForColumn;
860 int effectiveRow = 0;
861 foreach (int row, rowNodes) {
864 foreach (int cell, at(row).children)
865 if (at(cell).isTableCell()) {
866 // skip all columns with spans from previous rows
867 while (colsInRow < rowColSpanForColumn.size()) {
868 const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
870 if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
871 Q_ASSERT(spanInfo.col == colsInRow);
872 colsInRow += spanInfo.colSpan;
877 const QTextHtmlParserNode &c = at(cell);
878 const int currentColumn = colsInRow;
879 colsInRow += c.tableCellColSpan;
881 RowColSpanInfo spanInfo;
882 spanInfo.row = effectiveRow;
883 spanInfo.col = currentColumn;
884 spanInfo.colSpan = c.tableCellColSpan;
885 spanInfo.rowSpan = c.tableCellRowSpan;
886 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
887 rowColSpans.append(spanInfo);
889 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
890 rowColSpanForColumn.resize(columnWidths.size());
891 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
892 if (columnWidths.at(i).type() == QTextLength::VariableLength) {
893 QTextLength w = c.width;
894 if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
895 w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
898 rowColSpanForColumn[i] = spanInfo;
902 table.columns = qMax(table.columns, colsInRow);
906 table.rows = effectiveRow;
908 table.lastIndent = indent;
911 if (table.rows == 0 || table.columns == 0)
914 QTextFrameFormat fmt;
915 const QTextHtmlParserNode &node = at(tableNodeIdx);
917 if (!node.isTextFrame) {
918 QTextTableFormat tableFmt;
919 tableFmt.setCellSpacing(node.tableCellSpacing);
920 tableFmt.setCellPadding(node.tableCellPadding);
921 if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
922 tableFmt.setAlignment(node.blockFormat.alignment());
923 tableFmt.setColumns(table.columns);
924 tableFmt.setColumnWidthConstraints(columnWidths);
925 tableFmt.setHeaderRowCount(tableHeaderRowCount);
929 fmt.setTopMargin(topMargin(tableNodeIdx));
930 fmt.setBottomMargin(bottomMargin(tableNodeIdx));
931 fmt.setLeftMargin(leftMargin(tableNodeIdx)
932 + table.lastIndent * 40 // ##### not a good emulation
934 fmt.setRightMargin(rightMargin(tableNodeIdx));
937 if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
938 && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
939 && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
940 fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
942 fmt.setBorderStyle(node.borderStyle);
943 fmt.setBorderBrush(node.borderBrush);
944 fmt.setBorder(node.tableBorder);
945 fmt.setWidth(node.width);
946 fmt.setHeight(node.height);
947 if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
948 fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
950 if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
951 fmt.setLayoutDirection(node.blockFormat.layoutDirection());
952 if (node.charFormat.background().style() != Qt::NoBrush)
953 fmt.setBackground(node.charFormat.background());
954 fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
956 if (node.isTextFrame) {
957 if (node.isRootFrame) {
958 table.frame = cursor.currentFrame();
959 table.frame->setFrameFormat(fmt);
961 table.frame = cursor.insertFrame(fmt);
963 table.isTextFrame = true;
965 const int oldPos = cursor.position();
966 QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
967 table.frame = textTable;
969 for (int i = 0; i < rowColSpans.count(); ++i) {
970 const RowColSpanInfo &nfo = rowColSpans.at(i);
971 textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
974 table.currentCell = TableCellIterator(textTable);
975 cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
980 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
982 QTextBlockFormat block;
983 QTextCharFormat charFmt;
984 bool modifiedBlockFormat = true;
985 bool modifiedCharFormat = true;
987 if (currentNode->isTableCell() && !tables.isEmpty()) {
988 Table &t = tables.last();
989 if (!t.isTextFrame && !t.currentCell.atEnd()) {
990 QTextTableCell cell = t.currentCell.cell();
991 if (cell.isValid()) {
992 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
993 if (topPadding(currentNodeIdx) >= 0)
994 fmt.setTopPadding(topPadding(currentNodeIdx));
995 if (bottomPadding(currentNodeIdx) >= 0)
996 fmt.setBottomPadding(bottomPadding(currentNodeIdx));
997 if (leftPadding(currentNodeIdx) >= 0)
998 fmt.setLeftPadding(leftPadding(currentNodeIdx));
999 if (rightPadding(currentNodeIdx) >= 0)
1000 fmt.setRightPadding(rightPadding(currentNodeIdx));
1001 cell.setFormat(fmt);
1003 cursor.setPosition(cell.firstPosition());
1007 compressNextWhitespace = RemoveWhiteSpace;
1009 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1010 charFmt.setBackground(currentNode->charFormat.background());
1011 cursor.mergeBlockCharFormat(charFmt);
1016 block = cursor.blockFormat();
1017 charFmt = cursor.blockCharFormat();
1018 modifiedBlockFormat = false;
1019 modifiedCharFormat = false;
1024 qreal tm = qreal(topMargin(currentNodeIdx));
1025 if (tm > block.topMargin()) {
1026 block.setTopMargin(tm);
1027 modifiedBlockFormat = true;
1031 int bottomMargin = this->bottomMargin(currentNodeIdx);
1033 // for list items we may want to collapse with the bottom margin of the
1035 const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1036 if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1038 && (parentNode->isListStart() || parentNode->id == Html_dl)
1039 && (parentNode->children.last() == currentNodeIdx)) {
1040 bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1043 if (block.bottomMargin() != bottomMargin) {
1044 block.setBottomMargin(bottomMargin);
1045 modifiedBlockFormat = true;
1049 const qreal lm = leftMargin(currentNodeIdx);
1050 const qreal rm = rightMargin(currentNodeIdx);
1052 if (block.leftMargin() != lm) {
1053 block.setLeftMargin(lm);
1054 modifiedBlockFormat = true;
1056 if (block.rightMargin() != rm) {
1057 block.setRightMargin(rm);
1058 modifiedBlockFormat = true;
1062 if (currentNode->id != Html_li
1066 || !lists.last().list
1067 || lists.last().list->itemNumber(cursor.block()) == -1
1070 block.setIndent(indent);
1071 modifiedBlockFormat = true;
1074 if (currentNode->blockFormat.propertyCount() > 0) {
1075 modifiedBlockFormat = true;
1076 block.merge(currentNode->blockFormat);
1079 if (currentNode->charFormat.propertyCount() > 0) {
1080 modifiedCharFormat = true;
1081 charFmt.merge(currentNode->charFormat);
1084 // ####################
1085 // block.setFloatPosition(node->cssFloat);
1087 if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1088 block.setNonBreakableLines(true);
1089 modifiedBlockFormat = true;
1092 if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1093 block.setBackground(currentNode->charFormat.background());
1094 modifiedBlockFormat = true;
1097 if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1098 if (modifiedBlockFormat)
1099 cursor.setBlockFormat(block);
1100 if (modifiedCharFormat)
1101 cursor.setBlockCharFormat(charFmt);
1103 if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1104 cursor.setBlockFormat(block);
1105 cursor.setBlockCharFormat(charFmt);
1107 appendBlock(block, charFmt);
1111 if (currentNode->userState != -1)
1112 cursor.block().setUserState(currentNode->userState);
1114 if (currentNode->id == Html_li && !lists.isEmpty()) {
1115 List &l = lists.last();
1117 l.list->add(cursor.block());
1119 l.list = cursor.createList(l.format);
1120 const qreal listTopMargin = topMargin(l.listNode);
1121 if (listTopMargin > block.topMargin()) {
1122 block.setTopMargin(listTopMargin);
1123 cursor.mergeBlockFormat(block);
1127 QTextBlockFormat fmt;
1129 cursor.mergeBlockFormat(fmt);
1133 forceBlockMerging = false;
1134 if (currentNode->id == Html_body || currentNode->id == Html_html)
1135 forceBlockMerging = true;
1137 if (currentNode->isEmptyParagraph) {
1139 return ContinueWithNextNode;
1143 blockTagClosed = false;
1144 return ContinueWithCurrentNode;
1147 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1149 if (!namedAnchors.isEmpty()) {
1150 charFmt.setAnchor(true);
1151 charFmt.setAnchorNames(namedAnchors);
1152 namedAnchors.clear();
1155 cursor.insertBlock(format, charFmt);
1157 if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1158 compressNextWhitespace = RemoveWhiteSpace;
1161 #endif // QT_NO_TEXTHTMLPARSER
1164 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1166 Returns a QTextDocumentFragment based on the arbitrary piece of
1167 HTML in the given \a text. The formatting is preserved as much as
1168 possible; for example, "<b>bold</b>" will become a document
1169 fragment with the text "bold" with a bold character format.
1172 #ifndef QT_NO_TEXTHTMLPARSER
1174 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1176 return fromHtml(html, 0);
1180 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1183 Returns a QTextDocumentFragment based on the arbitrary piece of
1184 HTML in the given \a text. The formatting is preserved as much as
1185 possible; for example, "<b>bold</b>" will become a document
1186 fragment with the text "bold" with a bold character format.
1188 If the provided HTML contains references to external resources such as imported style sheets, then
1189 they will be loaded through the \a resourceProvider.
1192 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1194 QTextDocumentFragment res;
1195 res.d = new QTextDocumentFragmentPrivate;
1197 QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1203 #endif // QT_NO_TEXTHTMLPARSER