1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtextdocumentfragment.h"
43 #include "qtextdocumentfragment_p.h"
44 #include "qtextcursor_p.h"
45 #include "qtextlist.h"
48 #include <qtextcodec.h>
49 #include <qbytearray.h>
50 #include <qdatastream.h>
51 #include <qdatetime.h>
55 QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
56 #if defined(Q_CC_DIAB) // compiler bug
57 : formatCollection(*_destination.d->priv->formatCollection()), originalText((const QString)_source.d->priv->buffer())
59 : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
62 src = _source.d->priv;
63 dst = _destination.d->priv;
64 insertPos = _destination.position();
65 this->forceCharFormat = forceCharFormat;
66 primaryCharFormatIndex = convertFormatIndex(fmt);
70 int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
72 QTextFormat fmt = oldFormat;
73 if (objectIndexToSet != -1) {
74 fmt.setObjectIndex(objectIndexToSet);
75 } else if (fmt.objectIndex() != -1) {
76 int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
77 if (newObjectIndex == -1) {
78 QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex());
79 Q_ASSERT(objFormat.objectIndex() == -1);
80 newObjectIndex = formatCollection.createObjectIndex(objFormat);
81 objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
83 fmt.setObjectIndex(newObjectIndex);
85 int idx = formatCollection.indexForFormat(fmt);
86 Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type());
90 int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex)
92 QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos);
93 const QTextFragmentData * const frag = fragIt.value();
95 Q_ASSERT(objectIndex == -1
96 || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1));
100 charFormatIndex = primaryCharFormatIndex;
102 charFormatIndex = convertFormatIndex(frag->format, objectIndex);
104 const int inFragmentOffset = qMax(0, pos - fragIt.position());
105 int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos);
107 QTextBlock nextBlock = src->blocksFind(pos + 1);
110 if (nextBlock.position() == pos + 1) {
111 blockIdx = convertFormatIndex(nextBlock.blockFormat());
112 } else if (pos == 0 && insertPos == 0) {
113 dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat());
114 dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat());
117 QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy);
118 if (txtToInsert.length() == 1
119 && (txtToInsert.at(0) == QChar::ParagraphSeparator
120 || txtToInsert.at(0) == QTextBeginningOfFrame
121 || txtToInsert.at(0) == QTextEndOfFrame
124 dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex);
127 if (nextBlock.textList()) {
128 QTextBlock dstBlock = dst->blocksFind(insertPos);
129 if (!dstBlock.textList()) {
130 // insert a new text block with the block and char format from the
131 // source block to make sure that the following text fragments
132 // end up in a list as they should
133 int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat());
134 int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat());
135 dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex);
139 dst->insert(insertPos, txtToInsert, charFormatIndex);
140 const int userState = nextBlock.userState();
142 dst->blocksFind(insertPos).setUserState(userState);
143 insertPos += txtToInsert.length();
149 void QTextCopyHelper::appendFragments(int pos, int endPos)
151 Q_ASSERT(pos < endPos);
154 pos += appendFragment(pos, endPos);
157 void QTextCopyHelper::copy()
159 if (cursor.hasComplexSelection()) {
160 QTextTable *table = cursor.currentTable();
161 int row_start, col_start, num_rows, num_cols;
162 cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
164 QTextTableFormat tableFormat = table->format();
165 tableFormat.setColumns(num_cols);
166 tableFormat.clearColumnWidthConstraints();
167 const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat);
169 Q_ASSERT(row_start != -1);
170 for (int r = row_start; r < row_start + num_rows; ++r) {
171 for (int c = col_start; c < col_start + num_cols; ++c) {
172 QTextTableCell cell = table->cellAt(r, c);
173 const int rspan = cell.rowSpan();
174 const int cspan = cell.columnSpan();
181 int cc = cell.column();
186 // add the QTextBeginningOfFrame
187 QTextCharFormat cellFormat = cell.format();
188 if (r + rspan >= row_start + num_rows) {
189 cellFormat.setTableCellRowSpan(row_start + num_rows - r);
191 if (c + cspan >= col_start + num_cols) {
192 cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
194 const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex);
197 const int cellPos = cell.firstPosition();
198 QTextBlock block = src->blocksFind(cellPos);
199 if (block.position() == cellPos) {
200 blockIdx = convertFormatIndex(block.blockFormat());
203 dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex);
206 // nothing to add for empty cells
207 if (cell.lastPosition() > cellPos) {
209 appendFragments(cellPos, cell.lastPosition());
215 int end = table->lastPosition();
216 appendFragment(end, end+1, objectIndex);
218 appendFragments(cursor.selectionStart(), cursor.selectionEnd());
222 QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor)
223 : ref(1), doc(new QTextDocument), importedFromPlainText(false)
225 doc->setUndoRedoEnabled(false);
227 if (!_cursor.hasSelection())
230 doc->docHandle()->beginEditBlock();
231 QTextCursor destCursor(doc);
232 QTextCopyHelper(_cursor, destCursor).copy();
233 doc->docHandle()->endEditBlock();
236 doc->docHandle()->mergeCachedResources(_cursor.d->priv);
239 void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const
241 if (_cursor.isNull())
244 QTextDocumentPrivate *destPieceTable = _cursor.d->priv;
245 destPieceTable->beginEditBlock();
247 QTextCursor sourceCursor(doc);
248 sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
249 QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy();
251 destPieceTable->endEditBlock();
255 \class QTextDocumentFragment
258 \brief The QTextDocumentFragment class represents a piece of formatted text
259 from a QTextDocument.
261 \ingroup richtext-processing
264 A QTextDocumentFragment is a fragment of rich text, that can be inserted into
265 a QTextDocument. A document fragment can be created from a
266 QTextDocument, from a QTextCursor's selection, or from another
267 document fragment. Document fragments can also be created by the
268 static functions, fromPlainText() and fromHtml().
270 The contents of a document fragment can be obtained as plain text
271 by using the toPlainText() function, or it can be obtained as HTML
277 Constructs an empty QTextDocumentFragment.
281 QTextDocumentFragment::QTextDocumentFragment()
287 Converts the given \a document into a QTextDocumentFragment.
288 Note that the QTextDocumentFragment only stores the document contents, not meta information
289 like the document's title.
291 QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
297 QTextCursor cursor(const_cast<QTextDocument *>(document));
298 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
299 d = new QTextDocumentFragmentPrivate(cursor);
303 Creates a QTextDocumentFragment from the \a{cursor}'s selection.
304 If the cursor doesn't have a selection, the created fragment is empty.
306 \sa isEmpty() QTextCursor::selection()
308 QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
311 if (!cursor.hasSelection())
314 d = new QTextDocumentFragmentPrivate(cursor);
318 \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
320 Copy constructor. Creates a copy of the \a other fragment.
322 QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
330 \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
332 Assigns the \a other fragment to this fragment.
334 QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
338 if (d && !d->ref.deref())
345 Destroys the document fragment.
347 QTextDocumentFragment::~QTextDocumentFragment()
349 if (d && !d->ref.deref())
354 Returns true if the fragment is empty; otherwise returns false.
356 bool QTextDocumentFragment::isEmpty() const
358 return !d || !d->doc || d->doc->docHandle()->length() <= 1;
362 Returns the document fragment's text as plain text (i.e. with no
363 formatting information).
367 QString QTextDocumentFragment::toPlainText() const
372 return d->doc->toPlainText();
375 // #### Qt 5: merge with other overload
380 #ifndef QT_NO_TEXTHTMLPARSER
382 QString QTextDocumentFragment::toHtml() const
384 return toHtml(QByteArray());
390 Returns the contents of the document fragment as HTML,
391 using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
393 \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
395 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
400 return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
403 #endif // QT_NO_TEXTHTMLPARSER
406 Returns a document fragment that contains the given \a plainText.
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.
411 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
413 QTextDocumentFragment res;
415 res.d = new QTextDocumentFragmentPrivate;
416 res.d->importedFromPlainText = true;
417 QTextCursor cursor(res.d->doc);
418 cursor.insertText(plainText);
422 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
424 if (style == QTextListFormat::ListDisc)
425 return QTextListFormat::ListCircle;
426 else if (style == QTextListFormat::ListCircle)
427 return QTextListFormat::ListSquare;
431 #ifndef QT_NO_TEXTHTMLPARSER
433 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
434 : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
436 cursor = QTextCursor(doc);
437 wsm = QTextHtmlParserNode::WhiteSpaceNormal;
439 QString html = _html;
440 const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
441 if (startFragmentPos != -1) {
442 QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
445 const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
447 const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
448 if (startFragmentPos < endFragmentPos)
449 html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
451 html = html.mid(startFragmentPos);
453 if (hasQtRichtextMetaTag)
454 html.prepend(qt3RichTextHeader);
457 parse(html, resourceProvider ? resourceProvider : doc);
461 void QTextHtmlImporter::import()
463 cursor.beginEditBlock();
465 forceBlockMerging = false;
466 compressNextWhitespace = RemoveWhiteSpace;
467 blockTagClosed = false;
468 for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
469 currentNode = &at(currentNodeIdx);
470 wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
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
478 * 2) check if the current node is a special node like a
479 * <table>, <ul> or <img> tag that requires special processing
481 * 3) if the node should result in a QTextBlock create one and
482 * finally insert text that may be attached to the node
485 /* emit 'closing' table blocks or adjust current indent level
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
491 if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
492 blockTagClosed = closeTag();
493 // visually collapse subsequent block tags, but if the element after the closed block tag
494 // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
495 // hasBlock to false.
497 && !currentNode->isBlock()
498 && currentNode->id != Html_unknown)
501 } else if (hasBlock) {
502 // when collapsing subsequent block tags we need to clear the block format
503 QTextBlockFormat blockFormat = currentNode->blockFormat;
504 blockFormat.setIndent(indent);
506 QTextBlockFormat oldFormat = cursor.blockFormat();
507 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
508 QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
509 if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
510 /* We remove an empty paragrah that requested a page break after.
511 moving that request to the next paragraph means we also need to make
512 that a pagebreak before to keep the same visual appearance.
514 pageBreak = QTextFormat::PageBreak_AlwaysBefore;
515 blockFormat.setPageBreakPolicy(pageBreak);
518 cursor.setBlockFormat(blockFormat);
522 if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
523 if (currentNode->id == Html_title)
524 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
525 // ignore explicitly 'invisible' elements
529 if (processSpecialNodes() == ContinueWithNextNode)
532 // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
535 && !currentNode->isBlock()
536 && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
537 && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
539 QTextBlockFormat block = currentNode->blockFormat;
540 block.setIndent(indent);
542 appendBlock(block, currentNode->charFormat);
547 if (currentNode->isBlock()) {
548 QTextHtmlImporter::ProcessNodeResult result = processBlockNode();
549 if (result == ContinueWithNextNode) {
551 } else if (result == ContinueWithNextSibling) {
552 currentNodeIdx += currentNode->children.size();
557 if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
558 namedAnchors.append(currentNode->charFormat.anchorName());
561 if (appendNodeText())
562 hasBlock = false; // if we actually appended text then we don't
563 // have an empty block anymore
566 cursor.endEditBlock();
569 bool QTextHtmlImporter::appendNodeText()
571 const int initialCursorPosition = cursor.position();
572 QTextCharFormat format = currentNode->charFormat;
574 if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
575 compressNextWhitespace = PreserveWhiteSpace;
577 QString text = currentNode->text;
579 QString textToInsert;
580 textToInsert.reserve(text.size());
582 for (int i = 0; i < text.length(); ++i) {
583 QChar ch = text.at(i);
587 && ch != QChar::ParagraphSeparator) {
589 if (compressNextWhitespace == CollapseWhiteSpace)
590 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
591 else if(compressNextWhitespace == RemoveWhiteSpace)
594 if (wsm == QTextHtmlParserNode::WhiteSpacePre
597 if (ch == QLatin1Char('\n')) {
600 } else if (ch == QLatin1Char('\r')) {
603 } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
604 compressNextWhitespace = RemoveWhiteSpace;
605 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
608 ch = QLatin1Char(' ');
611 compressNextWhitespace = PreserveWhiteSpace;
614 if (ch == QLatin1Char('\n')
615 || ch == QChar::ParagraphSeparator) {
617 if (!textToInsert.isEmpty()) {
618 cursor.insertText(textToInsert, format);
619 textToInsert.clear();
622 QTextBlockFormat fmt = cursor.blockFormat();
624 if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
625 QTextBlockFormat tmp = fmt;
626 tmp.clearProperty(QTextFormat::BlockBottomMargin);
627 cursor.setBlockFormat(tmp);
630 fmt.clearProperty(QTextFormat::BlockTopMargin);
631 appendBlock(fmt, cursor.charFormat());
633 if (!namedAnchors.isEmpty()) {
634 if (!textToInsert.isEmpty()) {
635 cursor.insertText(textToInsert, format);
636 textToInsert.clear();
639 format.setAnchor(true);
640 format.setAnchorNames(namedAnchors);
641 cursor.insertText(ch, format);
642 namedAnchors.clear();
643 format.clearProperty(QTextFormat::IsAnchor);
644 format.clearProperty(QTextFormat::AnchorName);
651 if (!textToInsert.isEmpty()) {
652 cursor.insertText(textToInsert, format);
655 return cursor.position() != initialCursorPosition;
658 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
660 switch (currentNode->id) {
662 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
663 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
664 fmt.setBackground(currentNode->charFormat.background());
665 doc->rootFrame()->setFrameFormat(fmt);
666 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
668 compressNextWhitespace = RemoveWhiteSpace;
673 QTextListFormat::Style style = currentNode->listStyle;
675 if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
676 const QTextHtmlParserNode *n = &at(currentNode->parent);
678 if (n->id == Html_ul) {
679 style = nextListStyle(currentNode->listStyle);
688 QTextListFormat listFmt;
689 listFmt.setStyle(style);
690 if (!currentNode->textListNumberPrefix.isNull())
691 listFmt.setNumberPrefix(currentNode->textListNumberPrefix);
692 if (!currentNode->textListNumberSuffix.isNull())
693 listFmt.setNumberSuffix(currentNode->textListNumberSuffix);
696 if (currentNode->hasCssListIndent)
697 listFmt.setIndent(currentNode->cssListIndent);
699 listFmt.setIndent(indent);
703 l.listNode = currentNodeIdx;
705 compressNextWhitespace = RemoveWhiteSpace;
707 // broken html: <ul>Text here<li>Foo
708 const QString simpl = currentNode->text.simplified();
709 if (simpl.isEmpty() || simpl.at(0).isSpace())
710 return ContinueWithNextNode;
715 Table t = scanTable(currentNodeIdx);
718 compressNextWhitespace = RemoveWhiteSpace;
719 return ContinueWithNextNode;
723 return ContinueWithNextNode;
726 QTextImageFormat fmt;
727 fmt.setName(currentNode->imageName);
729 fmt.merge(currentNode->charFormat);
731 if (currentNode->imageWidth != -1)
732 fmt.setWidth(currentNode->imageWidth);
733 if (currentNode->imageHeight != -1)
734 fmt.setHeight(currentNode->imageHeight);
736 cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
738 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
739 cursor.mergeCharFormat(currentNode->charFormat);
740 cursor.movePosition(QTextCursor::Right);
741 compressNextWhitespace = CollapseWhiteSpace;
744 return ContinueWithNextNode;
748 QTextBlockFormat blockFormat = currentNode->blockFormat;
749 blockFormat.setTopMargin(topMargin(currentNodeIdx));
750 blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
751 blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
752 if (hasBlock && importMode == ImportToDocument)
753 cursor.mergeBlockFormat(blockFormat);
755 appendBlock(blockFormat);
757 compressNextWhitespace = RemoveWhiteSpace;
758 return ContinueWithNextNode;
763 return ContinueWithCurrentNode;
766 // returns true if a block tag was closed
767 bool QTextHtmlImporter::closeTag()
769 const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
770 const int endDepth = depth(currentNodeIdx) - 1;
771 int depth = this->depth(currentNodeIdx - 1);
772 bool blockTagClosed = false;
774 while (depth > endDepth) {
776 if (!tables.isEmpty())
779 switch (closedNode->id) {
781 if (t && !t->isTextFrame) {
784 // for broken html with rowspans but missing tr tags
785 while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
789 blockTagClosed = true;
795 indent = t->lastIndent;
797 tables.resize(tables.size() - 1);
800 if (tables.isEmpty()) {
801 cursor = doc->rootFrame()->lastCursorPosition();
805 cursor = t->frame->lastCursorPosition();
806 else if (!t->currentCell.atEnd())
807 cursor = t->currentCell.cell().lastCursorPosition();
810 // we don't need an extra block after tables, so we don't
811 // claim to have closed one for the creation of a new one
813 blockTagClosed = false;
814 compressNextWhitespace = RemoveWhiteSpace;
819 if (t && !t->isTextFrame)
821 blockTagClosed = true;
822 compressNextWhitespace = RemoveWhiteSpace;
829 lists.resize(lists.size() - 1);
831 blockTagClosed = true;
835 compressNextWhitespace = RemoveWhiteSpace;
839 if (closedNode->children.isEmpty())
843 if (closedNode->isBlock())
844 blockTagClosed = true;
848 closedNode = &at(closedNode->parent);
852 return blockTagClosed;
855 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
860 QVector<QTextLength> columnWidths;
862 int tableHeaderRowCount = 0;
863 QVector<int> rowNodes;
864 rowNodes.reserve(at(tableNodeIdx).children.count());
865 foreach (int row, at(tableNodeIdx).children)
866 switch (at(row).id) {
873 foreach (int potentialRow, at(row).children)
874 if (at(potentialRow).id == Html_tr) {
875 rowNodes += potentialRow;
876 if (at(row).id == Html_thead)
877 ++tableHeaderRowCount;
883 QVector<RowColSpanInfo> rowColSpans;
884 QVector<RowColSpanInfo> rowColSpanForColumn;
886 int effectiveRow = 0;
887 foreach (int row, rowNodes) {
890 foreach (int cell, at(row).children)
891 if (at(cell).isTableCell()) {
892 // skip all columns with spans from previous rows
893 while (colsInRow < rowColSpanForColumn.size()) {
894 const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
896 if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
897 Q_ASSERT(spanInfo.col == colsInRow);
898 colsInRow += spanInfo.colSpan;
903 const QTextHtmlParserNode &c = at(cell);
904 const int currentColumn = colsInRow;
905 colsInRow += c.tableCellColSpan;
907 RowColSpanInfo spanInfo;
908 spanInfo.row = effectiveRow;
909 spanInfo.col = currentColumn;
910 spanInfo.colSpan = c.tableCellColSpan;
911 spanInfo.rowSpan = c.tableCellRowSpan;
912 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
913 rowColSpans.append(spanInfo);
915 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
916 rowColSpanForColumn.resize(columnWidths.size());
917 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
918 if (columnWidths.at(i).type() == QTextLength::VariableLength) {
919 QTextLength w = c.width;
920 if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
921 w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
924 rowColSpanForColumn[i] = spanInfo;
928 table.columns = qMax(table.columns, colsInRow);
932 table.rows = effectiveRow;
934 table.lastIndent = indent;
937 if (table.rows == 0 || table.columns == 0)
940 QTextFrameFormat fmt;
941 const QTextHtmlParserNode &node = at(tableNodeIdx);
943 if (!node.isTextFrame) {
944 QTextTableFormat tableFmt;
945 tableFmt.setCellSpacing(node.tableCellSpacing);
946 tableFmt.setCellPadding(node.tableCellPadding);
947 if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
948 tableFmt.setAlignment(node.blockFormat.alignment());
949 tableFmt.setColumns(table.columns);
950 tableFmt.setColumnWidthConstraints(columnWidths);
951 tableFmt.setHeaderRowCount(tableHeaderRowCount);
955 fmt.setTopMargin(topMargin(tableNodeIdx));
956 fmt.setBottomMargin(bottomMargin(tableNodeIdx));
957 fmt.setLeftMargin(leftMargin(tableNodeIdx)
958 + table.lastIndent * 40 // ##### not a good emulation
960 fmt.setRightMargin(rightMargin(tableNodeIdx));
963 if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
964 && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
965 && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
966 fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
968 fmt.setBorderStyle(node.borderStyle);
969 fmt.setBorderBrush(node.borderBrush);
970 fmt.setBorder(node.tableBorder);
971 fmt.setWidth(node.width);
972 fmt.setHeight(node.height);
973 if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
974 fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
976 if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
977 fmt.setLayoutDirection(node.blockFormat.layoutDirection());
978 if (node.charFormat.background().style() != Qt::NoBrush)
979 fmt.setBackground(node.charFormat.background());
980 fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
982 if (node.isTextFrame) {
983 if (node.isRootFrame) {
984 table.frame = cursor.currentFrame();
985 table.frame->setFrameFormat(fmt);
987 table.frame = cursor.insertFrame(fmt);
989 table.isTextFrame = true;
991 const int oldPos = cursor.position();
992 QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
993 table.frame = textTable;
995 for (int i = 0; i < rowColSpans.count(); ++i) {
996 const RowColSpanInfo &nfo = rowColSpans.at(i);
997 textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
1000 table.currentCell = TableCellIterator(textTable);
1001 cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
1006 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
1008 QTextBlockFormat block;
1009 QTextCharFormat charFmt;
1010 bool modifiedBlockFormat = true;
1011 bool modifiedCharFormat = true;
1013 if (currentNode->isTableCell() && !tables.isEmpty()) {
1014 Table &t = tables.last();
1015 if (!t.isTextFrame && !t.currentCell.atEnd()) {
1016 QTextTableCell cell = t.currentCell.cell();
1017 if (cell.isValid()) {
1018 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
1019 if (topPadding(currentNodeIdx) >= 0)
1020 fmt.setTopPadding(topPadding(currentNodeIdx));
1021 if (bottomPadding(currentNodeIdx) >= 0)
1022 fmt.setBottomPadding(bottomPadding(currentNodeIdx));
1023 if (leftPadding(currentNodeIdx) >= 0)
1024 fmt.setLeftPadding(leftPadding(currentNodeIdx));
1025 if (rightPadding(currentNodeIdx) >= 0)
1026 fmt.setRightPadding(rightPadding(currentNodeIdx));
1027 cell.setFormat(fmt);
1029 cursor.setPosition(cell.firstPosition());
1033 compressNextWhitespace = RemoveWhiteSpace;
1035 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1036 charFmt.setBackground(currentNode->charFormat.background());
1037 cursor.mergeBlockCharFormat(charFmt);
1042 block = cursor.blockFormat();
1043 charFmt = cursor.blockCharFormat();
1044 modifiedBlockFormat = false;
1045 modifiedCharFormat = false;
1050 qreal tm = qreal(topMargin(currentNodeIdx));
1051 if (tm > block.topMargin()) {
1052 block.setTopMargin(tm);
1053 modifiedBlockFormat = true;
1057 int bottomMargin = this->bottomMargin(currentNodeIdx);
1059 // for list items we may want to collapse with the bottom margin of the
1061 const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1062 if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1064 && (parentNode->isListStart() || parentNode->id == Html_dl)
1065 && (parentNode->children.last() == currentNodeIdx)) {
1066 bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1069 if (block.bottomMargin() != bottomMargin) {
1070 block.setBottomMargin(bottomMargin);
1071 modifiedBlockFormat = true;
1075 const qreal lm = leftMargin(currentNodeIdx);
1076 const qreal rm = rightMargin(currentNodeIdx);
1078 if (block.leftMargin() != lm) {
1079 block.setLeftMargin(lm);
1080 modifiedBlockFormat = true;
1082 if (block.rightMargin() != rm) {
1083 block.setRightMargin(rm);
1084 modifiedBlockFormat = true;
1088 if (currentNode->id != Html_li
1092 || !lists.last().list
1093 || lists.last().list->itemNumber(cursor.block()) == -1
1096 block.setIndent(indent);
1097 modifiedBlockFormat = true;
1100 if (currentNode->blockFormat.propertyCount() > 0) {
1101 modifiedBlockFormat = true;
1102 block.merge(currentNode->blockFormat);
1105 if (currentNode->charFormat.propertyCount() > 0) {
1106 modifiedCharFormat = true;
1107 charFmt.merge(currentNode->charFormat);
1110 // ####################
1111 // block.setFloatPosition(node->cssFloat);
1113 if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1114 block.setNonBreakableLines(true);
1115 modifiedBlockFormat = true;
1118 if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1119 block.setBackground(currentNode->charFormat.background());
1120 modifiedBlockFormat = true;
1123 if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1124 if (modifiedBlockFormat)
1125 cursor.setBlockFormat(block);
1126 if (modifiedCharFormat)
1127 cursor.setBlockCharFormat(charFmt);
1129 if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1130 cursor.setBlockFormat(block);
1131 cursor.setBlockCharFormat(charFmt);
1133 appendBlock(block, charFmt);
1137 if (currentNode->userState != -1)
1138 cursor.block().setUserState(currentNode->userState);
1140 if (currentNode->id == Html_li && !lists.isEmpty()) {
1141 List &l = lists.last();
1143 l.list->add(cursor.block());
1145 l.list = cursor.createList(l.format);
1146 const qreal listTopMargin = topMargin(l.listNode);
1147 if (listTopMargin > block.topMargin()) {
1148 block.setTopMargin(listTopMargin);
1149 cursor.mergeBlockFormat(block);
1153 QTextBlockFormat fmt;
1155 cursor.mergeBlockFormat(fmt);
1159 forceBlockMerging = false;
1160 if (currentNode->id == Html_body || currentNode->id == Html_html)
1161 forceBlockMerging = true;
1163 if (currentNode->isEmptyParagraph) {
1165 return ContinueWithNextSibling;
1169 blockTagClosed = false;
1170 return ContinueWithCurrentNode;
1173 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1175 if (!namedAnchors.isEmpty()) {
1176 charFmt.setAnchor(true);
1177 charFmt.setAnchorNames(namedAnchors);
1178 namedAnchors.clear();
1181 cursor.insertBlock(format, charFmt);
1183 if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1184 compressNextWhitespace = RemoveWhiteSpace;
1187 #endif // QT_NO_TEXTHTMLPARSER
1190 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1192 Returns a QTextDocumentFragment based on the arbitrary piece of
1193 HTML in the given \a text. The formatting is preserved as much as
1194 possible; for example, "<b>bold</b>" will become a document
1195 fragment with the text "bold" with a bold character format.
1198 #ifndef QT_NO_TEXTHTMLPARSER
1200 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1202 return fromHtml(html, 0);
1206 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1209 Returns a QTextDocumentFragment based on the arbitrary piece of
1210 HTML in the given \a text. The formatting is preserved as much as
1211 possible; for example, "<b>bold</b>" will become a document
1212 fragment with the text "bold" with a bold character format.
1214 If the provided HTML contains references to external resources such as imported style sheets, then
1215 they will be loaded through the \a resourceProvider.
1218 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1220 QTextDocumentFragment res;
1221 res.d = new QTextDocumentFragmentPrivate;
1223 QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1229 #endif // QT_NO_TEXTHTMLPARSER