1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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 #ifndef QT_NO_TEXTHTMLPARSER
380 Returns the contents of the document fragment as HTML,
381 using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
383 \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
385 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
390 return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
393 #endif // QT_NO_TEXTHTMLPARSER
396 Returns a document fragment that contains the given \a plainText.
398 When inserting such a fragment into a QTextDocument the current char format of
399 the QTextCursor used for insertion is used as format for the text.
401 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
403 QTextDocumentFragment res;
405 res.d = new QTextDocumentFragmentPrivate;
406 res.d->importedFromPlainText = true;
407 QTextCursor cursor(res.d->doc);
408 cursor.insertText(plainText);
412 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
414 if (style == QTextListFormat::ListDisc)
415 return QTextListFormat::ListCircle;
416 else if (style == QTextListFormat::ListCircle)
417 return QTextListFormat::ListSquare;
421 #ifndef QT_NO_TEXTHTMLPARSER
423 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
424 : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
426 cursor = QTextCursor(doc);
427 wsm = QTextHtmlParserNode::WhiteSpaceNormal;
429 QString html = _html;
430 const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
431 if (startFragmentPos != -1) {
432 QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
435 const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
437 const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
438 if (startFragmentPos < endFragmentPos)
439 html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
441 html = html.mid(startFragmentPos);
443 if (hasQtRichtextMetaTag)
444 html.prepend(qt3RichTextHeader);
447 parse(html, resourceProvider ? resourceProvider : doc);
451 void QTextHtmlImporter::import()
453 cursor.beginEditBlock();
455 forceBlockMerging = false;
456 compressNextWhitespace = RemoveWhiteSpace;
457 blockTagClosed = false;
458 for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
459 currentNode = &at(currentNodeIdx);
460 wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
463 * process each node in three stages:
464 * 1) check if the hierarchy changed and we therefore passed the
465 * equivalent of a closing tag -> we may need to finish off
466 * some structures like tables
468 * 2) check if the current node is a special node like a
469 * <table>, <ul> or <img> tag that requires special processing
471 * 3) if the node should result in a QTextBlock create one and
472 * finally insert text that may be attached to the node
475 /* emit 'closing' table blocks or adjust current indent level
477 * 1) are beyond the first node
478 * 2) the current node not being a child of the previous node
479 * means there was a tag closing in the input html
481 if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
482 blockTagClosed = closeTag();
483 // visually collapse subsequent block tags, but if the element after the closed block tag
484 // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
485 // hasBlock to false.
487 && !currentNode->isBlock()
488 && currentNode->id != Html_unknown)
491 } else if (hasBlock) {
492 // when collapsing subsequent block tags we need to clear the block format
493 QTextBlockFormat blockFormat = currentNode->blockFormat;
494 blockFormat.setIndent(indent);
496 QTextBlockFormat oldFormat = cursor.blockFormat();
497 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
498 QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
499 if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
500 /* We remove an empty paragrah that requested a page break after.
501 moving that request to the next paragraph means we also need to make
502 that a pagebreak before to keep the same visual appearance.
504 pageBreak = QTextFormat::PageBreak_AlwaysBefore;
505 blockFormat.setPageBreakPolicy(pageBreak);
508 cursor.setBlockFormat(blockFormat);
512 if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
513 if (currentNode->id == Html_title)
514 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
515 // ignore explicitly 'invisible' elements
519 if (processSpecialNodes() == ContinueWithNextNode)
522 // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
525 && !currentNode->isBlock()
526 && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
527 && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
529 QTextBlockFormat block = currentNode->blockFormat;
530 block.setIndent(indent);
532 appendBlock(block, currentNode->charFormat);
537 if (currentNode->isBlock()) {
538 QTextHtmlImporter::ProcessNodeResult result = processBlockNode();
539 if (result == ContinueWithNextNode) {
541 } else if (result == ContinueWithNextSibling) {
542 currentNodeIdx += currentNode->children.size();
547 if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
548 namedAnchors.append(currentNode->charFormat.anchorName());
551 if (appendNodeText())
552 hasBlock = false; // if we actually appended text then we don't
553 // have an empty block anymore
556 cursor.endEditBlock();
559 bool QTextHtmlImporter::appendNodeText()
561 const int initialCursorPosition = cursor.position();
562 QTextCharFormat format = currentNode->charFormat;
564 if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
565 compressNextWhitespace = PreserveWhiteSpace;
567 QString text = currentNode->text;
569 QString textToInsert;
570 textToInsert.reserve(text.size());
572 for (int i = 0; i < text.length(); ++i) {
573 QChar ch = text.at(i);
577 && ch != QChar::ParagraphSeparator) {
579 if (compressNextWhitespace == CollapseWhiteSpace)
580 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
581 else if(compressNextWhitespace == RemoveWhiteSpace)
584 if (wsm == QTextHtmlParserNode::WhiteSpacePre
587 if (ch == QLatin1Char('\n')) {
590 } else if (ch == QLatin1Char('\r')) {
593 } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
594 compressNextWhitespace = RemoveWhiteSpace;
595 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
598 ch = QLatin1Char(' ');
601 compressNextWhitespace = PreserveWhiteSpace;
604 if (ch == QLatin1Char('\n')
605 || ch == QChar::ParagraphSeparator) {
607 if (!textToInsert.isEmpty()) {
608 cursor.insertText(textToInsert, format);
609 textToInsert.clear();
612 QTextBlockFormat fmt = cursor.blockFormat();
614 if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
615 QTextBlockFormat tmp = fmt;
616 tmp.clearProperty(QTextFormat::BlockBottomMargin);
617 cursor.setBlockFormat(tmp);
620 fmt.clearProperty(QTextFormat::BlockTopMargin);
621 appendBlock(fmt, cursor.charFormat());
623 if (!namedAnchors.isEmpty()) {
624 if (!textToInsert.isEmpty()) {
625 cursor.insertText(textToInsert, format);
626 textToInsert.clear();
629 format.setAnchor(true);
630 format.setAnchorNames(namedAnchors);
631 cursor.insertText(ch, format);
632 namedAnchors.clear();
633 format.clearProperty(QTextFormat::IsAnchor);
634 format.clearProperty(QTextFormat::AnchorName);
641 if (!textToInsert.isEmpty()) {
642 cursor.insertText(textToInsert, format);
645 return cursor.position() != initialCursorPosition;
648 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
650 switch (currentNode->id) {
652 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
653 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
654 fmt.setBackground(currentNode->charFormat.background());
655 doc->rootFrame()->setFrameFormat(fmt);
656 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
658 compressNextWhitespace = RemoveWhiteSpace;
663 QTextListFormat::Style style = currentNode->listStyle;
665 if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
666 const QTextHtmlParserNode *n = &at(currentNode->parent);
668 if (n->id == Html_ul) {
669 style = nextListStyle(currentNode->listStyle);
678 QTextListFormat listFmt;
679 listFmt.setStyle(style);
680 if (!currentNode->textListNumberPrefix.isNull())
681 listFmt.setNumberPrefix(currentNode->textListNumberPrefix);
682 if (!currentNode->textListNumberSuffix.isNull())
683 listFmt.setNumberSuffix(currentNode->textListNumberSuffix);
686 if (currentNode->hasCssListIndent)
687 listFmt.setIndent(currentNode->cssListIndent);
689 listFmt.setIndent(indent);
693 l.listNode = currentNodeIdx;
695 compressNextWhitespace = RemoveWhiteSpace;
697 // broken html: <ul>Text here<li>Foo
698 const QString simpl = currentNode->text.simplified();
699 if (simpl.isEmpty() || simpl.at(0).isSpace())
700 return ContinueWithNextNode;
705 Table t = scanTable(currentNodeIdx);
708 compressNextWhitespace = RemoveWhiteSpace;
709 return ContinueWithNextNode;
713 return ContinueWithNextNode;
716 QTextImageFormat fmt;
717 fmt.setName(currentNode->imageName);
719 fmt.merge(currentNode->charFormat);
721 if (currentNode->imageWidth != -1)
722 fmt.setWidth(currentNode->imageWidth);
723 if (currentNode->imageHeight != -1)
724 fmt.setHeight(currentNode->imageHeight);
726 cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
728 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
729 cursor.mergeCharFormat(currentNode->charFormat);
730 cursor.movePosition(QTextCursor::Right);
731 compressNextWhitespace = CollapseWhiteSpace;
734 return ContinueWithNextNode;
738 QTextBlockFormat blockFormat = currentNode->blockFormat;
739 blockFormat.setTopMargin(topMargin(currentNodeIdx));
740 blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
741 blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
742 if (hasBlock && importMode == ImportToDocument)
743 cursor.mergeBlockFormat(blockFormat);
745 appendBlock(blockFormat);
747 compressNextWhitespace = RemoveWhiteSpace;
748 return ContinueWithNextNode;
753 return ContinueWithCurrentNode;
756 // returns true if a block tag was closed
757 bool QTextHtmlImporter::closeTag()
759 const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
760 const int endDepth = depth(currentNodeIdx) - 1;
761 int depth = this->depth(currentNodeIdx - 1);
762 bool blockTagClosed = false;
764 while (depth > endDepth) {
766 if (!tables.isEmpty())
769 switch (closedNode->id) {
771 if (t && !t->isTextFrame) {
774 // for broken html with rowspans but missing tr tags
775 while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
779 blockTagClosed = true;
785 indent = t->lastIndent;
787 tables.resize(tables.size() - 1);
790 if (tables.isEmpty()) {
791 cursor = doc->rootFrame()->lastCursorPosition();
795 cursor = t->frame->lastCursorPosition();
796 else if (!t->currentCell.atEnd())
797 cursor = t->currentCell.cell().lastCursorPosition();
800 // we don't need an extra block after tables, so we don't
801 // claim to have closed one for the creation of a new one
803 blockTagClosed = false;
804 compressNextWhitespace = RemoveWhiteSpace;
809 if (t && !t->isTextFrame)
811 blockTagClosed = true;
812 compressNextWhitespace = RemoveWhiteSpace;
819 lists.resize(lists.size() - 1);
821 blockTagClosed = true;
825 compressNextWhitespace = RemoveWhiteSpace;
829 if (closedNode->children.isEmpty())
833 if (closedNode->isBlock())
834 blockTagClosed = true;
838 closedNode = &at(closedNode->parent);
842 return blockTagClosed;
845 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
850 QVector<QTextLength> columnWidths;
852 int tableHeaderRowCount = 0;
853 QVector<int> rowNodes;
854 rowNodes.reserve(at(tableNodeIdx).children.count());
855 foreach (int row, at(tableNodeIdx).children)
856 switch (at(row).id) {
863 foreach (int potentialRow, at(row).children)
864 if (at(potentialRow).id == Html_tr) {
865 rowNodes += potentialRow;
866 if (at(row).id == Html_thead)
867 ++tableHeaderRowCount;
873 QVector<RowColSpanInfo> rowColSpans;
874 QVector<RowColSpanInfo> rowColSpanForColumn;
876 int effectiveRow = 0;
877 foreach (int row, rowNodes) {
880 foreach (int cell, at(row).children)
881 if (at(cell).isTableCell()) {
882 // skip all columns with spans from previous rows
883 while (colsInRow < rowColSpanForColumn.size()) {
884 const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
886 if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
887 Q_ASSERT(spanInfo.col == colsInRow);
888 colsInRow += spanInfo.colSpan;
893 const QTextHtmlParserNode &c = at(cell);
894 const int currentColumn = colsInRow;
895 colsInRow += c.tableCellColSpan;
897 RowColSpanInfo spanInfo;
898 spanInfo.row = effectiveRow;
899 spanInfo.col = currentColumn;
900 spanInfo.colSpan = c.tableCellColSpan;
901 spanInfo.rowSpan = c.tableCellRowSpan;
902 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
903 rowColSpans.append(spanInfo);
905 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
906 rowColSpanForColumn.resize(columnWidths.size());
907 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
908 if (columnWidths.at(i).type() == QTextLength::VariableLength) {
909 QTextLength w = c.width;
910 if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
911 w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
914 rowColSpanForColumn[i] = spanInfo;
918 table.columns = qMax(table.columns, colsInRow);
922 table.rows = effectiveRow;
924 table.lastIndent = indent;
927 if (table.rows == 0 || table.columns == 0)
930 QTextFrameFormat fmt;
931 const QTextHtmlParserNode &node = at(tableNodeIdx);
933 if (!node.isTextFrame) {
934 QTextTableFormat tableFmt;
935 tableFmt.setCellSpacing(node.tableCellSpacing);
936 tableFmt.setCellPadding(node.tableCellPadding);
937 if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
938 tableFmt.setAlignment(node.blockFormat.alignment());
939 tableFmt.setColumns(table.columns);
940 tableFmt.setColumnWidthConstraints(columnWidths);
941 tableFmt.setHeaderRowCount(tableHeaderRowCount);
945 fmt.setTopMargin(topMargin(tableNodeIdx));
946 fmt.setBottomMargin(bottomMargin(tableNodeIdx));
947 fmt.setLeftMargin(leftMargin(tableNodeIdx)
948 + table.lastIndent * 40 // ##### not a good emulation
950 fmt.setRightMargin(rightMargin(tableNodeIdx));
953 if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
954 && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
955 && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
956 fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
958 fmt.setBorderStyle(node.borderStyle);
959 fmt.setBorderBrush(node.borderBrush);
960 fmt.setBorder(node.tableBorder);
961 fmt.setWidth(node.width);
962 fmt.setHeight(node.height);
963 if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
964 fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
966 if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
967 fmt.setLayoutDirection(node.blockFormat.layoutDirection());
968 if (node.charFormat.background().style() != Qt::NoBrush)
969 fmt.setBackground(node.charFormat.background());
970 fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
972 if (node.isTextFrame) {
973 if (node.isRootFrame) {
974 table.frame = cursor.currentFrame();
975 table.frame->setFrameFormat(fmt);
977 table.frame = cursor.insertFrame(fmt);
979 table.isTextFrame = true;
981 const int oldPos = cursor.position();
982 QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
983 table.frame = textTable;
985 for (int i = 0; i < rowColSpans.count(); ++i) {
986 const RowColSpanInfo &nfo = rowColSpans.at(i);
987 textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
990 table.currentCell = TableCellIterator(textTable);
991 cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
996 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
998 QTextBlockFormat block;
999 QTextCharFormat charFmt;
1000 bool modifiedBlockFormat = true;
1001 bool modifiedCharFormat = true;
1003 if (currentNode->isTableCell() && !tables.isEmpty()) {
1004 Table &t = tables.last();
1005 if (!t.isTextFrame && !t.currentCell.atEnd()) {
1006 QTextTableCell cell = t.currentCell.cell();
1007 if (cell.isValid()) {
1008 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
1009 if (topPadding(currentNodeIdx) >= 0)
1010 fmt.setTopPadding(topPadding(currentNodeIdx));
1011 if (bottomPadding(currentNodeIdx) >= 0)
1012 fmt.setBottomPadding(bottomPadding(currentNodeIdx));
1013 if (leftPadding(currentNodeIdx) >= 0)
1014 fmt.setLeftPadding(leftPadding(currentNodeIdx));
1015 if (rightPadding(currentNodeIdx) >= 0)
1016 fmt.setRightPadding(rightPadding(currentNodeIdx));
1017 cell.setFormat(fmt);
1019 cursor.setPosition(cell.firstPosition());
1023 compressNextWhitespace = RemoveWhiteSpace;
1025 if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1026 charFmt.setBackground(currentNode->charFormat.background());
1027 cursor.mergeBlockCharFormat(charFmt);
1032 block = cursor.blockFormat();
1033 charFmt = cursor.blockCharFormat();
1034 modifiedBlockFormat = false;
1035 modifiedCharFormat = false;
1040 qreal tm = qreal(topMargin(currentNodeIdx));
1041 if (tm > block.topMargin()) {
1042 block.setTopMargin(tm);
1043 modifiedBlockFormat = true;
1047 int bottomMargin = this->bottomMargin(currentNodeIdx);
1049 // for list items we may want to collapse with the bottom margin of the
1051 const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1052 if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1054 && (parentNode->isListStart() || parentNode->id == Html_dl)
1055 && (parentNode->children.last() == currentNodeIdx)) {
1056 bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1059 if (block.bottomMargin() != bottomMargin) {
1060 block.setBottomMargin(bottomMargin);
1061 modifiedBlockFormat = true;
1065 const qreal lm = leftMargin(currentNodeIdx);
1066 const qreal rm = rightMargin(currentNodeIdx);
1068 if (block.leftMargin() != lm) {
1069 block.setLeftMargin(lm);
1070 modifiedBlockFormat = true;
1072 if (block.rightMargin() != rm) {
1073 block.setRightMargin(rm);
1074 modifiedBlockFormat = true;
1078 if (currentNode->id != Html_li
1082 || !lists.last().list
1083 || lists.last().list->itemNumber(cursor.block()) == -1
1086 block.setIndent(indent);
1087 modifiedBlockFormat = true;
1090 if (currentNode->blockFormat.propertyCount() > 0) {
1091 modifiedBlockFormat = true;
1092 block.merge(currentNode->blockFormat);
1095 if (currentNode->charFormat.propertyCount() > 0) {
1096 modifiedCharFormat = true;
1097 charFmt.merge(currentNode->charFormat);
1100 // ####################
1101 // block.setFloatPosition(node->cssFloat);
1103 if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1104 block.setNonBreakableLines(true);
1105 modifiedBlockFormat = true;
1108 if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1109 block.setBackground(currentNode->charFormat.background());
1110 modifiedBlockFormat = true;
1113 if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1114 if (modifiedBlockFormat)
1115 cursor.setBlockFormat(block);
1116 if (modifiedCharFormat)
1117 cursor.setBlockCharFormat(charFmt);
1119 if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1120 cursor.setBlockFormat(block);
1121 cursor.setBlockCharFormat(charFmt);
1123 appendBlock(block, charFmt);
1127 if (currentNode->userState != -1)
1128 cursor.block().setUserState(currentNode->userState);
1130 if (currentNode->id == Html_li && !lists.isEmpty()) {
1131 List &l = lists.last();
1133 l.list->add(cursor.block());
1135 l.list = cursor.createList(l.format);
1136 const qreal listTopMargin = topMargin(l.listNode);
1137 if (listTopMargin > block.topMargin()) {
1138 block.setTopMargin(listTopMargin);
1139 cursor.mergeBlockFormat(block);
1143 QTextBlockFormat fmt;
1145 cursor.mergeBlockFormat(fmt);
1149 forceBlockMerging = false;
1150 if (currentNode->id == Html_body || currentNode->id == Html_html)
1151 forceBlockMerging = true;
1153 if (currentNode->isEmptyParagraph) {
1155 return ContinueWithNextSibling;
1159 blockTagClosed = false;
1160 return ContinueWithCurrentNode;
1163 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1165 if (!namedAnchors.isEmpty()) {
1166 charFmt.setAnchor(true);
1167 charFmt.setAnchorNames(namedAnchors);
1168 namedAnchors.clear();
1171 cursor.insertBlock(format, charFmt);
1173 if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1174 compressNextWhitespace = RemoveWhiteSpace;
1177 #endif // QT_NO_TEXTHTMLPARSER
1180 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1182 Returns a QTextDocumentFragment based on the arbitrary piece of
1183 HTML in the given \a text. The formatting is preserved as much as
1184 possible; for example, "<b>bold</b>" will become a document
1185 fragment with the text "bold" with a bold character format.
1188 #ifndef QT_NO_TEXTHTMLPARSER
1190 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1192 return fromHtml(html, 0);
1196 \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1199 Returns a QTextDocumentFragment based on the arbitrary piece of
1200 HTML in the given \a text. The formatting is preserved as much as
1201 possible; for example, "<b>bold</b>" will become a document
1202 fragment with the text "bold" with a bold character format.
1204 If the provided HTML contains references to external resources such as imported style sheets, then
1205 they will be loaded through the \a resourceProvider.
1208 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1210 QTextDocumentFragment res;
1211 res.d = new QTextDocumentFragmentPrivate;
1213 QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1219 #endif // QT_NO_TEXTHTMLPARSER