/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Copyright (C) 2016-2019 Ivailo Monev ** ** This file is part of the QtGui module of the Katie Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtextdocumentfragment.h" #include "qtextdocumentfragment_p.h" #include "qtextcursor_p.h" #include "qtextlist.h" #include "qdebug.h" #include "qtextcodec.h" #include "qbytearray.h" #include "qdatastream.h" #include "qdatetime.h" QT_BEGIN_NAMESPACE QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool _forceCharFormat, const QTextCharFormat &fmt) : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer()) { src = _source.d->priv; dst = _destination.d->priv; insertPos = _destination.position(); forceCharFormat = _forceCharFormat; primaryCharFormatIndex = convertFormatIndex(fmt); cursor = _source; } int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet) { QTextFormat fmt = oldFormat; if (objectIndexToSet != -1) { fmt.setObjectIndex(objectIndexToSet); } else if (fmt.objectIndex() != -1) { int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1); if (newObjectIndex == -1) { QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex()); Q_ASSERT(objFormat.objectIndex() == -1); newObjectIndex = formatCollection.createObjectIndex(objFormat); objectIndexMap.insert(fmt.objectIndex(), newObjectIndex); } fmt.setObjectIndex(newObjectIndex); } int idx = formatCollection.indexForFormat(fmt); Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type()); return idx; } int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex) { QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos); const QTextFragmentData * const frag = fragIt.value(); Q_ASSERT(objectIndex == -1 || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1)); int charFormatIndex; if (forceCharFormat) charFormatIndex = primaryCharFormatIndex; else charFormatIndex = convertFormatIndex(frag->format, objectIndex); const int inFragmentOffset = qMax(0, pos - fragIt.position()); int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos); QTextBlock nextBlock = src->blocksFind(pos + 1); int blockIdx = -2; if (nextBlock.position() == pos + 1) { blockIdx = convertFormatIndex(nextBlock.blockFormat()); } else if (pos == 0 && insertPos == 0) { dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat()); dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat()); } QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy); if (txtToInsert.length() == 1 && (txtToInsert.at(0) == QChar::ParagraphSeparator || txtToInsert.at(0) == QTextBeginningOfFrame || txtToInsert.at(0) == QTextEndOfFrame ) ) { dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex); ++insertPos; } else { if (nextBlock.textList()) { QTextBlock dstBlock = dst->blocksFind(insertPos); if (!dstBlock.textList()) { // insert a new text block with the block and char format from the // source block to make sure that the following text fragments // end up in a list as they should int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat()); int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat()); dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex); ++insertPos; } } dst->insert(insertPos, txtToInsert, charFormatIndex); const int userState = nextBlock.userState(); if (userState != -1) dst->blocksFind(insertPos).setUserState(userState); insertPos += txtToInsert.length(); } return charsToCopy; } void QTextCopyHelper::appendFragments(int pos, int endPos) { Q_ASSERT(pos < endPos); while (pos < endPos) pos += appendFragment(pos, endPos); } void QTextCopyHelper::copy() { if (cursor.hasComplexSelection()) { QTextTable *table = cursor.currentTable(); int row_start, col_start, num_rows, num_cols; cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); QTextTableFormat tableFormat = table->format(); tableFormat.setColumns(num_cols); tableFormat.clearColumnWidthConstraints(); const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat); Q_ASSERT(row_start != -1); for (int r = row_start; r < row_start + num_rows; ++r) { for (int c = col_start; c < col_start + num_cols; ++c) { QTextTableCell cell = table->cellAt(r, c); const int rspan = cell.rowSpan(); const int cspan = cell.columnSpan(); if (rspan != 1) { int cr = cell.row(); if (cr != r) continue; } if (cspan != 1) { int cc = cell.column(); if (cc != c) continue; } // add the QTextBeginningOfFrame QTextCharFormat cellFormat = cell.format(); if (r + rspan >= row_start + num_rows) { cellFormat.setTableCellRowSpan(row_start + num_rows - r); } if (c + cspan >= col_start + num_cols) { cellFormat.setTableCellColumnSpan(col_start + num_cols - c); } const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex); int blockIdx = -2; const int cellPos = cell.firstPosition(); QTextBlock block = src->blocksFind(cellPos); if (block.position() == cellPos) { blockIdx = convertFormatIndex(block.blockFormat()); } dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex); ++insertPos; // nothing to add for empty cells if (cell.lastPosition() > cellPos) { // add the contents appendFragments(cellPos, cell.lastPosition()); } } } // add end of table int end = table->lastPosition(); appendFragment(end, end+1, objectIndex); } else { appendFragments(cursor.selectionStart(), cursor.selectionEnd()); } } QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor) : ref(1), doc(new QTextDocument), importedFromPlainText(false) { doc->setUndoRedoEnabled(false); if (!_cursor.hasSelection()) return; doc->docHandle()->beginEditBlock(); QTextCursor destCursor(doc); QTextCopyHelper(_cursor, destCursor).copy(); doc->docHandle()->endEditBlock(); if (_cursor.d) doc->docHandle()->mergeCachedResources(_cursor.d->priv); } void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const { if (_cursor.isNull()) return; QTextDocumentPrivate *destPieceTable = _cursor.d->priv; destPieceTable->beginEditBlock(); QTextCursor sourceCursor(doc); sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy(); destPieceTable->endEditBlock(); } /*! \class QTextDocumentFragment \reentrant \brief The QTextDocumentFragment class represents a piece of formatted text from a QTextDocument. \ingroup richtext-processing \ingroup shared A QTextDocumentFragment is a fragment of rich text, that can be inserted into a QTextDocument. A document fragment can be created from a QTextDocument, from a QTextCursor's selection, or from another document fragment. Document fragments can also be created by the static functions, fromPlainText() and fromHtml(). The contents of a document fragment can be obtained as plain text by using the toPlainText() function, or it can be obtained as HTML with toHtml(). */ /*! Constructs an empty QTextDocumentFragment. \sa isEmpty() */ QTextDocumentFragment::QTextDocumentFragment() : d(0) { } /*! Converts the given \a document into a QTextDocumentFragment. Note that the QTextDocumentFragment only stores the document contents, not meta information like the document's title. */ QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document) : d(0) { if (!document) return; QTextCursor cursor(const_cast(document)); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); d = new QTextDocumentFragmentPrivate(cursor); } /*! Creates a QTextDocumentFragment from the \a{cursor}'s selection. If the cursor doesn't have a selection, the created fragment is empty. \sa isEmpty() QTextCursor::selection() */ QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor) : d(0) { if (!cursor.hasSelection()) return; d = new QTextDocumentFragmentPrivate(cursor); } /*! \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other) Copy constructor. Creates a copy of the \a other fragment. */ QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs) : d(rhs.d) { if (d) d->ref.ref(); } /*! \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other) Assigns the \a other fragment to this fragment. */ QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs) { if (rhs.d) rhs.d->ref.ref(); if (d && !d->ref.deref()) delete d; d = rhs.d; return *this; } /*! Destroys the document fragment. */ QTextDocumentFragment::~QTextDocumentFragment() { if (d && !d->ref.deref()) delete d; } /*! Returns true if the fragment is empty; otherwise returns false. */ bool QTextDocumentFragment::isEmpty() const { return !d || !d->doc || d->doc->docHandle()->length() <= 1; } /*! Returns the document fragment's text as plain text (i.e. with no formatting information). \sa toHtml() */ QString QTextDocumentFragment::toPlainText() const { if (!d) return QString(); return d->doc->toPlainText(); } #ifndef QT_NO_TEXTHTMLPARSER /*! \since 4.2 Returns the contents of the document fragment as HTML, using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1"). \sa toPlainText(), QTextDocument::toHtml(), QTextCodec */ QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const { if (!d) return QString(); return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment); } #endif // QT_NO_TEXTHTMLPARSER /*! Returns a document fragment that contains the given \a plainText. When inserting such a fragment into a QTextDocument the current char format of the QTextCursor used for insertion is used as format for the text. */ QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText) { QTextDocumentFragment res; res.d = new QTextDocumentFragmentPrivate; res.d->importedFromPlainText = true; QTextCursor cursor(res.d->doc); cursor.insertText(plainText); return res; } static QTextListFormat::Style nextListStyle(QTextListFormat::Style style) { if (style == QTextListFormat::ListDisc) return QTextListFormat::ListCircle; else if (style == QTextListFormat::ListCircle) return QTextListFormat::ListSquare; return style; } #ifndef QT_NO_TEXTHTMLPARSER QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider) : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode) { cursor = QTextCursor(doc); wsm = QTextHtmlParserNode::WhiteSpaceNormal; QString html = _html; const int startFragmentPos = html.indexOf(QLatin1String("")); if (startFragmentPos != -1) { QLatin1String qt3RichTextHeader(""); // Hack for Qt3 const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader); const int endFragmentPos = html.indexOf(QLatin1String("")); if (startFragmentPos < endFragmentPos) html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos); else html = html.mid(startFragmentPos); if (hasQtRichtextMetaTag) html.prepend(qt3RichTextHeader); } parse(html, resourceProvider ? resourceProvider : doc); // dumpHtml(); } void QTextHtmlImporter::import() { cursor.beginEditBlock(); hasBlock = true; forceBlockMerging = false; compressNextWhitespace = RemoveWhiteSpace; blockTagClosed = false; for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) { currentNode = &at(currentNodeIdx); wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm; /* * process each node in three stages: * 1) check if the hierarchy changed and we therefore passed the * equivalent of a closing tag -> we may need to finish off * some structures like tables * * 2) check if the current node is a special node like a * ,