Add QTextMarkdownWriter, QTextEdit::markdown property etc.
A QTextDocument can now be written out in Markdown format. - Add the QTextMarkdownWriter as a private class for now - Add QTextDocument::toMarkdown() - QTextDocumentWriter uses QTextMarkdownWriter if setFormat("markdown") is called or if the file suffix is .md or .mkd - Add QTextEdit::toMarkdown() and the markdown property [ChangeLog][QtGui][Text] Markdown (CommonMark or GitHub dialect) is now a supported format for reading into and writing from QTextDocument. Change-Id: I663a77017fac7ae1b3f9a400f5cd357bb40750af Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
This commit is contained in:
parent
9ec564b0bf
commit
23c2da3cc2
@ -1611,6 +1611,12 @@
|
||||
"condition": "libs.libmd4c",
|
||||
"output": [ "publicFeature" ]
|
||||
},
|
||||
"textmarkdownwriter": {
|
||||
"label": "MarkdownWriter",
|
||||
"purpose": "Provides a Markdown (CommonMark) writer",
|
||||
"section": "Kernel",
|
||||
"output": [ "publicFeature" ]
|
||||
},
|
||||
"textodfwriter": {
|
||||
"label": "OdfWriter",
|
||||
"purpose": "Provides an ODF writer.",
|
||||
@ -1892,7 +1898,7 @@ QMAKE_LIBDIR_OPENGL[_ES2] and QMAKE_LIBS_OPENGL[_ES2] in the mkspec for your pla
|
||||
{
|
||||
"section": "Text formats",
|
||||
"entries": [
|
||||
"texthtmlparser", "cssparser", "textodfwriter", "textmarkdownreader", "system-textmarkdownreader"
|
||||
"texthtmlparser", "cssparser", "textodfwriter", "textmarkdownreader", "system-textmarkdownreader", "textmarkdownwriter"
|
||||
]
|
||||
},
|
||||
"egl",
|
||||
|
@ -73,6 +73,9 @@
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
#include <private/qtextmarkdownimporter_p.h>
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
#include <private/qtextmarkdownwriter_p.h>
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
@ -3288,6 +3291,22 @@ QString QTextDocument::toHtml(const QByteArray &encoding) const
|
||||
}
|
||||
#endif // QT_NO_TEXTHTMLPARSER
|
||||
|
||||
/*!
|
||||
Returns a string containing a Markdown representation of the document,
|
||||
or an empty string if writing fails for any reason.
|
||||
*/
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) const
|
||||
{
|
||||
QString ret;
|
||||
QTextStream s(&ret);
|
||||
QTextMarkdownWriter w(s, features);
|
||||
if (w.writeAll(*this))
|
||||
return ret;
|
||||
return QString();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
Replaces the entire contents of the document with the given
|
||||
Markdown-formatted text in the \a markdown string, with the given
|
||||
@ -3301,8 +3320,19 @@ QString QTextDocument::toHtml(const QByteArray &encoding) const
|
||||
|
||||
Parsing of HTML included in the \a markdown string is handled in the same
|
||||
way as in \l setHtml; however, Markdown formatting inside HTML blocks is
|
||||
not supported. The \c MarkdownNoHTML feature flag can be set to disable
|
||||
HTML parsing.
|
||||
not supported.
|
||||
|
||||
Some features of the parser can be enabled or disabled via the \a features
|
||||
argument:
|
||||
|
||||
\value MarkdownNoHTML
|
||||
Any HTML tags in the Markdown text will be discarded
|
||||
\value MarkdownDialectCommonMark
|
||||
The parser supports only the features standardized by CommonMark
|
||||
\value MarkdownDialectGitHub
|
||||
The parser supports the GitHub dialect
|
||||
|
||||
The default is \c MarkdownDialectGitHub.
|
||||
|
||||
The undo/redo history is reset when this function is called.
|
||||
*/
|
||||
|
@ -151,7 +151,7 @@ public:
|
||||
void setHtml(const QString &html);
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
#if QT_CONFIG(textmarkdownwriter) || QT_CONFIG(textmarkdownreader)
|
||||
// Must be in sync with QTextMarkdownImporter::Features, should be in sync with #define MD_FLAG_* in md4c
|
||||
enum MarkdownFeature {
|
||||
MarkdownNoHTML = 0x0020 | 0x0040,
|
||||
@ -160,7 +160,13 @@ public:
|
||||
};
|
||||
Q_DECLARE_FLAGS(MarkdownFeatures, MarkdownFeature)
|
||||
Q_FLAG(MarkdownFeatures)
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString toMarkdown(MarkdownFeatures features = MarkdownDialectGitHub) const;
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
void setMarkdown(const QString &markdown, MarkdownFeatures features = MarkdownDialectGitHub);
|
||||
#endif
|
||||
|
||||
|
@ -51,6 +51,9 @@
|
||||
|
||||
#include "qtextdocumentfragment_p.h"
|
||||
#include "qtextodfwriter_p.h"
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
#include "qtextmarkdownwriter_p.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@ -267,6 +270,18 @@ bool QTextDocumentWriter::write(const QTextDocument *document)
|
||||
}
|
||||
#endif // QT_NO_TEXTODFWRITER
|
||||
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
if (format == "md" || format == "mkd" || format == "markdown") {
|
||||
if (!d->device->isWritable() && !d->device->open(QIODevice::WriteOnly)) {
|
||||
qWarning("QTextDocumentWriter::write: the device can not be opened for writing");
|
||||
return false;
|
||||
}
|
||||
QTextStream s(d->device);
|
||||
QTextMarkdownWriter writer(s, QTextDocument::MarkdownDialectGitHub);
|
||||
return writer.writeAll(*document);
|
||||
}
|
||||
#endif // textmarkdownwriter
|
||||
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
if (format == "html" || format == "htm") {
|
||||
if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) {
|
||||
@ -348,6 +363,7 @@ QTextCodec *QTextDocumentWriter::codec() const
|
||||
\header \li Format \li Description
|
||||
\row \li plaintext \li Plain text
|
||||
\row \li HTML \li HyperText Markup Language
|
||||
\row \li markdown \li Markdown (CommonMark or GitHub dialects)
|
||||
\row \li ODF \li OpenDocument Format
|
||||
\endtable
|
||||
|
||||
@ -364,6 +380,9 @@ QList<QByteArray> QTextDocumentWriter::supportedDocumentFormats()
|
||||
#ifndef QT_NO_TEXTODFWRITER
|
||||
answer << "ODF";
|
||||
#endif // QT_NO_TEXTODFWRITER
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
answer << "markdown";
|
||||
#endif
|
||||
|
||||
std::sort(answer.begin(), answer.end());
|
||||
return answer;
|
||||
|
363
src/gui/text/qtextmarkdownwriter.cpp
Normal file
363
src/gui/text/qtextmarkdownwriter.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt 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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://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 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qtextmarkdownwriter_p.h"
|
||||
#include "qtextdocumentlayout_p.h"
|
||||
#include "qfontinfo.h"
|
||||
#include "qfontmetrics.h"
|
||||
#include "qtextdocument_p.h"
|
||||
#include "qtextlist.h"
|
||||
#include "qtexttable.h"
|
||||
#include "qtextcursor.h"
|
||||
#include "qtextimagehandler_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static const QChar Space = QLatin1Char(' ');
|
||||
static const QChar Newline = QLatin1Char('\n');
|
||||
static const QChar Backtick = QLatin1Char('`');
|
||||
|
||||
QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features)
|
||||
: m_stream(stream), m_features(features)
|
||||
{
|
||||
}
|
||||
|
||||
bool QTextMarkdownWriter::writeAll(const QTextDocument &document)
|
||||
{
|
||||
writeFrame(document.rootFrame());
|
||||
return true;
|
||||
}
|
||||
|
||||
void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
|
||||
{
|
||||
Q_ASSERT(frame);
|
||||
const QTextTable *table = qobject_cast<const QTextTable*> (frame);
|
||||
QTextFrame::iterator iterator = frame->begin();
|
||||
QTextFrame *child = 0;
|
||||
int tableRow = -1;
|
||||
bool lastWasList = false;
|
||||
QVector<int> tableColumnWidths;
|
||||
if (table) {
|
||||
tableColumnWidths.resize(table->columns());
|
||||
for (int col = 0; col < table->columns(); ++col) {
|
||||
for (int row = 0; row < table->rows(); ++ row) {
|
||||
QTextTableCell cell = table->cellAt(row, col);
|
||||
int cellTextLen = 0;
|
||||
auto it = cell.begin();
|
||||
while (it != cell.end()) {
|
||||
QTextBlock block = it.currentBlock();
|
||||
if (block.isValid())
|
||||
cellTextLen += block.text().length();
|
||||
++it;
|
||||
}
|
||||
if (cell.columnSpan() == 1 && tableColumnWidths[col] < cellTextLen)
|
||||
tableColumnWidths[col] = cellTextLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!iterator.atEnd()) {
|
||||
if (iterator.currentFrame() && child != iterator.currentFrame())
|
||||
writeFrame(iterator.currentFrame());
|
||||
else { // no frame, it's a block
|
||||
QTextBlock block = iterator.currentBlock();
|
||||
if (table) {
|
||||
QTextTableCell cell = table->cellAt(block.position());
|
||||
if (tableRow < cell.row()) {
|
||||
if (tableRow == 0) {
|
||||
m_stream << Newline;
|
||||
for (int col = 0; col < tableColumnWidths.length(); ++col)
|
||||
m_stream << '|' << QString(tableColumnWidths[col], QLatin1Char('-'));
|
||||
m_stream << '|';
|
||||
}
|
||||
m_stream << Newline << "|";
|
||||
tableRow = cell.row();
|
||||
}
|
||||
} else if (!block.textList()) {
|
||||
if (lastWasList)
|
||||
m_stream << Newline;
|
||||
}
|
||||
int endingCol = writeBlock(block, !table, table && tableRow == 0);
|
||||
if (table) {
|
||||
QTextTableCell cell = table->cellAt(block.position());
|
||||
int paddingLen = -endingCol;
|
||||
int spanEndCol = cell.column() + cell.columnSpan();
|
||||
for (int col = cell.column(); col < spanEndCol; ++col)
|
||||
paddingLen += tableColumnWidths[col];
|
||||
if (paddingLen > 0)
|
||||
m_stream << QString(paddingLen, Space);
|
||||
for (int col = cell.column(); col < spanEndCol; ++col)
|
||||
m_stream << "|";
|
||||
} else if (block.textList()) {
|
||||
m_stream << Newline;
|
||||
} else if (endingCol > 0) {
|
||||
m_stream << Newline << Newline;
|
||||
}
|
||||
lastWasList = block.textList();
|
||||
}
|
||||
child = iterator.currentFrame();
|
||||
++iterator;
|
||||
}
|
||||
if (table)
|
||||
m_stream << Newline << Newline;
|
||||
}
|
||||
|
||||
static int nearestWordWrapIndex(const QString &s, int before)
|
||||
{
|
||||
before = qMin(before, s.length());
|
||||
for (int i = before - 1; i >= 0; --i) {
|
||||
if (s.at(i).isSpace())
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int adjacentBackticksCount(const QString &s)
|
||||
{
|
||||
int start = -1, len = s.length();
|
||||
int ret = 0;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (s.at(i) == Backtick) {
|
||||
if (start < 0)
|
||||
start = i;
|
||||
} else if (start >= 0) {
|
||||
ret = qMax(ret, i - start);
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
if (s.at(len - 1) == Backtick)
|
||||
ret = qMax(ret, len - start);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void maybeEscapeFirstChar(QString &s)
|
||||
{
|
||||
QString sTrimmed = s.trimmed();
|
||||
if (sTrimmed.isEmpty())
|
||||
return;
|
||||
char firstChar = sTrimmed.at(0).toLatin1();
|
||||
if (firstChar == '*' || firstChar == '+' || firstChar == '-') {
|
||||
int i = s.indexOf(QLatin1Char(firstChar));
|
||||
s.insert(i, QLatin1Char('\\'));
|
||||
}
|
||||
}
|
||||
|
||||
int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat)
|
||||
{
|
||||
int ColumnLimit = 80;
|
||||
int wrapIndent = 0;
|
||||
if (block.textList()) { // it's a list-item
|
||||
auto fmt = block.textList()->format();
|
||||
const int listLevel = fmt.indent();
|
||||
const int number = block.textList()->itemNumber(block) + 1;
|
||||
QByteArray bullet = " ";
|
||||
bool numeric = false;
|
||||
switch (fmt.style()) {
|
||||
case QTextListFormat::ListDisc: bullet = "-"; break;
|
||||
case QTextListFormat::ListCircle: bullet = "*"; break;
|
||||
case QTextListFormat::ListSquare: bullet = "+"; break;
|
||||
case QTextListFormat::ListStyleUndefined: break;
|
||||
case QTextListFormat::ListDecimal:
|
||||
case QTextListFormat::ListLowerAlpha:
|
||||
case QTextListFormat::ListUpperAlpha:
|
||||
case QTextListFormat::ListLowerRoman:
|
||||
case QTextListFormat::ListUpperRoman:
|
||||
numeric = true;
|
||||
break;
|
||||
}
|
||||
switch (block.blockFormat().marker()) {
|
||||
case QTextBlockFormat::Checked:
|
||||
bullet += " [x]";
|
||||
break;
|
||||
case QTextBlockFormat::Unchecked:
|
||||
bullet += " [ ]";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QString prefix((listLevel - 1) * (numeric ? 4 : 2), Space);
|
||||
if (numeric)
|
||||
prefix += QString::number(number) + fmt.numberSuffix() + Space;
|
||||
else
|
||||
prefix += QLatin1String(bullet) + Space;
|
||||
m_stream << prefix;
|
||||
wrapIndent = prefix.length();
|
||||
}
|
||||
|
||||
if (block.blockFormat().headingLevel())
|
||||
m_stream << QByteArray(block.blockFormat().headingLevel(), '#') << ' ';
|
||||
|
||||
QString wrapIndentString(wrapIndent, Space);
|
||||
// It would be convenient if QTextStream had a lineCharPos() accessor,
|
||||
// to keep track of how many characters (not bytes) have been written on the current line,
|
||||
// but it doesn't. So we have to keep track with this col variable.
|
||||
int col = wrapIndent;
|
||||
bool mono = false;
|
||||
bool startsOrEndsWithBacktick = false;
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
bool underline = false;
|
||||
bool strikeOut = false;
|
||||
QString backticks(Backtick);
|
||||
for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
|
||||
QString fragmentText = frag.fragment().text();
|
||||
while (fragmentText.endsWith(QLatin1Char('\n')))
|
||||
fragmentText.chop(1);
|
||||
startsOrEndsWithBacktick |= fragmentText.startsWith(Backtick) || fragmentText.endsWith(Backtick);
|
||||
QTextCharFormat fmt = frag.fragment().charFormat();
|
||||
if (fmt.isImageFormat()) {
|
||||
QTextImageFormat ifmt = fmt.toImageFormat();
|
||||
QString s = QLatin1String("![image](") + ifmt.name() + QLatin1Char(')');
|
||||
if (wrap && col + s.length() > ColumnLimit) {
|
||||
m_stream << Newline << wrapIndentString;
|
||||
col = wrapIndent;
|
||||
}
|
||||
m_stream << s;
|
||||
col += s.length();
|
||||
} else if (fmt.hasProperty(QTextFormat::AnchorHref)) {
|
||||
QString s = QLatin1Char('[') + fragmentText + QLatin1String("](") +
|
||||
fmt.property(QTextFormat::AnchorHref).toString() + QLatin1Char(')');
|
||||
if (wrap && col + s.length() > ColumnLimit) {
|
||||
m_stream << Newline << wrapIndentString;
|
||||
col = wrapIndent;
|
||||
}
|
||||
m_stream << s;
|
||||
col += s.length();
|
||||
} else {
|
||||
QFontInfo fontInfo(fmt.font());
|
||||
bool monoFrag = fontInfo.fixedPitch();
|
||||
QString markers;
|
||||
if (!ignoreFormat) {
|
||||
if (monoFrag != mono) {
|
||||
if (monoFrag)
|
||||
backticks = QString::fromLatin1(QByteArray(adjacentBackticksCount(fragmentText) + 1, '`'));
|
||||
markers += backticks;
|
||||
if (startsOrEndsWithBacktick)
|
||||
markers += Space;
|
||||
mono = monoFrag;
|
||||
}
|
||||
if (!block.blockFormat().headingLevel() && !mono) {
|
||||
if (fmt.font().bold() != bold) {
|
||||
markers += QLatin1String("**");
|
||||
bold = fmt.font().bold();
|
||||
}
|
||||
if (fmt.font().italic() != italic) {
|
||||
markers += QLatin1Char('*');
|
||||
italic = fmt.font().italic();
|
||||
}
|
||||
if (fmt.font().strikeOut() != strikeOut) {
|
||||
markers += QLatin1String("~~");
|
||||
strikeOut = fmt.font().strikeOut();
|
||||
}
|
||||
if (fmt.font().underline() != underline) {
|
||||
// Markdown doesn't support underline, but the parser will treat a single underline
|
||||
// the same as a single asterisk, and the marked fragment will be rendered in italics.
|
||||
// That will have to do.
|
||||
markers += QLatin1Char('_');
|
||||
underline = fmt.font().underline();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wrap && col + markers.length() * 2 + fragmentText.length() > ColumnLimit) {
|
||||
int i = 0;
|
||||
int fragLen = fragmentText.length();
|
||||
bool breakingLine = false;
|
||||
while (i < fragLen) {
|
||||
int j = i + ColumnLimit - col;
|
||||
if (j < fragLen) {
|
||||
int wi = nearestWordWrapIndex(fragmentText, j);
|
||||
if (wi < 0) {
|
||||
j = fragLen;
|
||||
} else {
|
||||
j = wi;
|
||||
breakingLine = true;
|
||||
}
|
||||
} else {
|
||||
j = fragLen;
|
||||
breakingLine = false;
|
||||
}
|
||||
QString subfrag = fragmentText.mid(i, j - i);
|
||||
if (!i) {
|
||||
m_stream << markers;
|
||||
col += markers.length();
|
||||
}
|
||||
if (col == wrapIndent)
|
||||
maybeEscapeFirstChar(subfrag);
|
||||
m_stream << subfrag;
|
||||
if (breakingLine) {
|
||||
m_stream << Newline << wrapIndentString;
|
||||
col = wrapIndent;
|
||||
} else {
|
||||
col += subfrag.length();
|
||||
}
|
||||
i = j + 1;
|
||||
}
|
||||
} else {
|
||||
m_stream << markers << fragmentText;
|
||||
col += markers.length() + fragmentText.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mono) {
|
||||
if (startsOrEndsWithBacktick) {
|
||||
m_stream << Space;
|
||||
col += 1;
|
||||
}
|
||||
m_stream << backticks;
|
||||
col += backticks.size();
|
||||
}
|
||||
if (bold) {
|
||||
m_stream << "**";
|
||||
col += 2;
|
||||
}
|
||||
if (italic) {
|
||||
m_stream << "*";
|
||||
col += 1;
|
||||
}
|
||||
if (underline) {
|
||||
m_stream << "_";
|
||||
col += 1;
|
||||
}
|
||||
if (strikeOut) {
|
||||
m_stream << "~~";
|
||||
col += 2;
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
78
src/gui/text/qtextmarkdownwriter_p.h
Normal file
78
src/gui/text/qtextmarkdownwriter_p.h
Normal file
@ -0,0 +1,78 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtGui module of the Qt 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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://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 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTEXTMARKDOWNWRITER_P_H
|
||||
#define QTEXTMARKDOWNWRITER_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/private/qtguiglobal_p.h>
|
||||
#include <QtCore/QTextStream>
|
||||
|
||||
#include "qtextdocument_p.h"
|
||||
#include "qtextdocumentwriter.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Q_GUI_EXPORT QTextMarkdownWriter
|
||||
{
|
||||
public:
|
||||
QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features);
|
||||
bool writeAll(const QTextDocument &document);
|
||||
|
||||
int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat);
|
||||
void writeFrame(const QTextFrame *frame);
|
||||
|
||||
private:
|
||||
QTextStream &m_stream;
|
||||
QTextDocument::MarkdownFeatures m_features;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QTEXTMARKDOWNWRITER_P_H
|
@ -109,6 +109,13 @@ qtConfig(textmarkdownreader) {
|
||||
text/qtextmarkdownimporter.cpp
|
||||
}
|
||||
|
||||
qtConfig(textmarkdownwriter) {
|
||||
HEADERS += \
|
||||
text/qtextmarkdownwriter_p.h
|
||||
SOURCES += \
|
||||
text/qtextmarkdownwriter.cpp
|
||||
}
|
||||
|
||||
qtConfig(cssparser) {
|
||||
HEADERS += \
|
||||
text/qcssparser_p.h
|
||||
|
@ -366,8 +366,8 @@ void QTextEditPrivate::_q_ensureVisible(const QRectF &_rect)
|
||||
\section1 Introduction and Concepts
|
||||
|
||||
QTextEdit is an advanced WYSIWYG viewer/editor supporting rich
|
||||
text formatting using HTML-style tags. It is optimized to handle
|
||||
large documents and to respond quickly to user input.
|
||||
text formatting using HTML-style tags, or Markdown format. It is optimized
|
||||
to handle large documents and to respond quickly to user input.
|
||||
|
||||
QTextEdit works on paragraphs and characters. A paragraph is a
|
||||
formatted string which is word-wrapped to fit into the width of
|
||||
@ -381,7 +381,7 @@ void QTextEditPrivate::_q_ensureVisible(const QRectF &_rect)
|
||||
QTextEdit can display images, lists and tables. If the text is
|
||||
too large to view within the text edit's viewport, scroll bars will
|
||||
appear. The text edit can load both plain text and rich text files.
|
||||
Rich text is described using a subset of HTML 4 markup, refer to the
|
||||
Rich text can be described using a subset of HTML 4 markup; refer to the
|
||||
\l {Supported HTML Subset} page for more information.
|
||||
|
||||
If you just need to display a small piece of rich text use QLabel.
|
||||
@ -401,12 +401,19 @@ void QTextEditPrivate::_q_ensureVisible(const QRectF &_rect)
|
||||
QTextEdit can display a large HTML subset, including tables and
|
||||
images.
|
||||
|
||||
The text is set or replaced using setHtml() which deletes any
|
||||
The text can be set or replaced using \l setHtml() which deletes any
|
||||
existing text and replaces it with the text passed in the
|
||||
setHtml() call. If you call setHtml() with legacy HTML, and then
|
||||
call toHtml(), the text that is returned may have different markup,
|
||||
but will render the same. The entire text can be deleted with clear().
|
||||
|
||||
Text can also be set or replaced using \l setMarkdown(), and the same
|
||||
caveats apply: if you then call \l toMarkdown(), the text that is returned
|
||||
may be different, but the meaning is preserved as much as possible.
|
||||
Markdown with some embedded HTML can be parsed, with the same limitations
|
||||
that \l setHtml() has; but \l toMarkdown() only writes "pure" Markdown,
|
||||
without any embedded HTML.
|
||||
|
||||
Text itself can be inserted using the QTextCursor class or using the
|
||||
convenience functions insertHtml(), insertPlainText(), append() or
|
||||
paste(). QTextCursor is also able to insert complex objects like tables
|
||||
@ -1213,11 +1220,54 @@ QString QTextEdit::toHtml() const
|
||||
}
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader) && QT_CONFIG(textmarkdownwriter)
|
||||
/*!
|
||||
\property QTextEdit::markdown
|
||||
|
||||
This property provides a Markdown interface to the text of the text edit.
|
||||
|
||||
\c toMarkdown() returns the text of the text edit as "pure" Markdown,
|
||||
without any embedded HTML formatting. Some features that QTextDocument
|
||||
supports (such as the use of specific colors and named fonts) cannot be
|
||||
expressed in "pure" Markdown, and they will be omitted.
|
||||
|
||||
\c setMarkdown() changes the text of the text edit. Any previous text is
|
||||
removed and the undo/redo history is cleared. The input text is
|
||||
interpreted as rich text in Markdown format.
|
||||
|
||||
Parsing of HTML included in the \a markdown string is handled in the same
|
||||
way as in \l setHtml; however, Markdown formatting inside HTML blocks is
|
||||
not supported.
|
||||
|
||||
Some features of the parser can be enabled or disabled via the \a features
|
||||
argument:
|
||||
|
||||
\value MarkdownNoHTML
|
||||
Any HTML tags in the Markdown text will be discarded
|
||||
\value MarkdownDialectCommonMark
|
||||
The parser supports only the features standardized by CommonMark
|
||||
\value MarkdownDialectGitHub
|
||||
The parser supports the GitHub dialect
|
||||
|
||||
The default is \c MarkdownDialectGitHub.
|
||||
|
||||
\sa plainText, html, QTextDocument::toMarkdown(), QTextDocument::setMarkdown()
|
||||
*/
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
void QTextEdit::setMarkdown(const QString &text)
|
||||
void QTextEdit::setMarkdown(const QString &markdown)
|
||||
{
|
||||
Q_D(const QTextEdit);
|
||||
d->control->setMarkdown(text);
|
||||
d->control->setMarkdown(markdown);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString QTextEdit::toMarkdown(QTextDocument::MarkdownFeatures features) const
|
||||
{
|
||||
Q_D(const QTextEdit);
|
||||
return d->control->toMarkdown(features);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -71,6 +71,9 @@ class Q_WIDGETS_EXPORT QTextEdit : public QAbstractScrollArea
|
||||
QDOC_PROPERTY(QTextOption::WrapMode wordWrapMode READ wordWrapMode WRITE setWordWrapMode)
|
||||
Q_PROPERTY(int lineWrapColumnOrWidth READ lineWrapColumnOrWidth WRITE setLineWrapColumnOrWidth)
|
||||
Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly)
|
||||
#if QT_CONFIG(textmarkdownreader) && QT_CONFIG(textmarkdownwriter)
|
||||
Q_PROPERTY(QString markdown READ toMarkdown WRITE setMarkdown NOTIFY textChanged)
|
||||
#endif
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true)
|
||||
#endif
|
||||
@ -174,6 +177,9 @@ public:
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
QString toHtml() const;
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString toMarkdown(QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub) const;
|
||||
#endif
|
||||
|
||||
void ensureCursorVisible();
|
||||
|
||||
@ -239,7 +245,7 @@ public Q_SLOTS:
|
||||
void setHtml(const QString &text);
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownreader)
|
||||
void setMarkdown(const QString &text);
|
||||
void setMarkdown(const QString &markdown);
|
||||
#endif
|
||||
void setText(const QString &text);
|
||||
|
||||
|
@ -3130,6 +3130,13 @@ QString QWidgetTextControl::toHtml() const
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
QString QWidgetTextControl::toMarkdown(QTextDocument::MarkdownFeatures features) const
|
||||
{
|
||||
return document()->toMarkdown(features);
|
||||
}
|
||||
#endif
|
||||
|
||||
void QWidgetTextControlPrivate::append(const QString &text, Qt::TextFormat format)
|
||||
{
|
||||
QTextCursor tmp(doc);
|
||||
|
@ -128,6 +128,9 @@ public:
|
||||
#ifndef QT_NO_TEXTHTMLPARSER
|
||||
QString toHtml() const;
|
||||
#endif
|
||||
#if QT_CONFIG(textmarkdownwriter)
|
||||
QString toMarkdown(QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub) const;
|
||||
#endif
|
||||
|
||||
virtual void ensureCursorVisible();
|
||||
|
||||
|
3
tests/auto/gui/text/qtextmarkdownwriter/BLACKLIST
Normal file
3
tests/auto/gui/text/qtextmarkdownwriter/BLACKLIST
Normal file
@ -0,0 +1,3 @@
|
||||
[rewriteDocument]
|
||||
winrt # QTBUG-54623
|
||||
|
95
tests/auto/gui/text/qtextmarkdownwriter/data/example.md
Normal file
95
tests/auto/gui/text/qtextmarkdownwriter/data/example.md
Normal file
@ -0,0 +1,95 @@
|
||||
# QTextEdit
|
||||
|
||||
The QTextEdit widget is an advanced editor that supports formatted rich text.
|
||||
It can be used to display HTML and other rich document formats. Internally,
|
||||
QTextEdit uses the QTextDocument class to describe both the high-level
|
||||
structure of each document and the low-level formatting of paragraphs.
|
||||
|
||||
If you are viewing this document in the textedit example, you can edit this
|
||||
document to explore Qt's rich text editing features. We have included some
|
||||
comments in each of the following sections to encourage you to experiment.
|
||||
|
||||
## Font and Paragraph Styles
|
||||
|
||||
QTextEdit supports **bold**, *italic*, and ~~strikethrough~~ font styles, and can
|
||||
display multicolored text. Font families such as Times New Roman and `Courier`
|
||||
can also be used directly. *If you place the cursor in a region of styled text,
|
||||
the controls in the tool bars will change to reflect the current style.*
|
||||
|
||||
Paragraphs can be formatted so that the text is left-aligned, right-aligned,
|
||||
centered, or fully justified.
|
||||
|
||||
*Try changing the alignment of some text and resize the editor to see how the
|
||||
text layout changes.*
|
||||
|
||||
## Lists
|
||||
|
||||
Different kinds of lists can be included in rich text documents. Standard
|
||||
bullet lists can be nested, using different symbols for each level of the list:
|
||||
|
||||
* Disc symbols are typically used for top-level list items.
|
||||
- Circle symbols can be used to distinguish between items in lower-level
|
||||
lists.
|
||||
+ Square symbols provide a reasonable alternative to discs and circles.
|
||||
|
||||
Ordered lists can be created that can be used for tables of contents. Different
|
||||
characters can be used to enumerate items, and we can use both Roman and Arabic
|
||||
numerals in the same list structure:
|
||||
|
||||
1. Introduction
|
||||
2. Qt Tools
|
||||
1) Qt Assistant
|
||||
2) Qt Designer
|
||||
1. Form Editor
|
||||
2. Component Architecture
|
||||
3) Qt Linguist
|
||||
|
||||
The list will automatically be renumbered if you add or remove items. *Try
|
||||
adding new sections to the above list or removing existing item to see the
|
||||
numbers change.*
|
||||
|
||||
## Images
|
||||
|
||||
Inline images are treated like ordinary ranges of characters in the text
|
||||
editor, so they flow with the surrounding text. Images can also be selected in
|
||||
the same way as text, making it easy to cut, copy, and paste them.
|
||||
|
||||
![image](images/logo32.png) *Try to select this image by clicking and dragging
|
||||
over it with the mouse, or use the text cursor to select it by holding down
|
||||
Shift and using the arrow keys. You can then cut or copy it, and paste it into
|
||||
different parts of this document.*
|
||||
|
||||
## Tables
|
||||
|
||||
QTextEdit can arrange and format tables, supporting features such as row and
|
||||
column spans, text formatting within cells, and size constraints for columns.
|
||||
|
||||
|
||||
| |Development Tools |Programming Techniques |Graphical User Interfaces|
|
||||
|-------------|------------------------------------|---------------------------|-------------------------|
|
||||
|9:00 - 11:00 |Introduction to Qt |||
|
||||
|11:00 - 13:00|Using qmake |Object-oriented Programming|Layouts in Qt |
|
||||
|13:00 - 15:00|Qt Designer Tutorial |Extreme Programming |Writing Custom Styles |
|
||||
|15:00 - 17:00|Qt Linguist and Internationalization| | |
|
||||
|
||||
*Try adding text to the cells in the table and experiment with the alignment of
|
||||
the paragraphs.*
|
||||
|
||||
## Hyperlinks
|
||||
|
||||
QTextEdit is designed to support hyperlinks between documents, and this feature
|
||||
is used extensively in
|
||||
[Qt Assistant](http://doc.qt.io/qt-5/qtassistant-index.html). Hyperlinks are
|
||||
automatically created when an HTML file is imported into an editor. Since the
|
||||
rich text framework supports hyperlinks natively, they can also be created
|
||||
programatically.
|
||||
|
||||
## Undo and Redo
|
||||
|
||||
Full support for undo and redo operations is built into QTextEdit and the
|
||||
underlying rich text framework. Operations on a document can be packaged
|
||||
together to make editing a more comfortable experience for the user.
|
||||
|
||||
*Try making changes to this document and press `Ctrl+Z` to undo them. You can
|
||||
always recover the original contents of the document.*
|
||||
|
@ -0,0 +1,7 @@
|
||||
CONFIG += testcase
|
||||
TARGET = tst_qtextmarkdownwriter
|
||||
QT += core-private gui-private testlib
|
||||
SOURCES += tst_qtextmarkdownwriter.cpp
|
||||
TESTDATA += data/example.md
|
||||
|
||||
DEFINES += SRCDIR=\\\"$$PWD\\\"
|
@ -0,0 +1,360 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** 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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <QTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlock>
|
||||
#include <QTextList>
|
||||
#include <QTextTable>
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
|
||||
#include <private/qtextmarkdownwriter_p.h>
|
||||
|
||||
// #define DEBUG_WRITE_OUTPUT
|
||||
|
||||
class tst_QTextMarkdownWriter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
private slots:
|
||||
void testWriteParagraph_data();
|
||||
void testWriteParagraph();
|
||||
void testWriteList();
|
||||
void testWriteNestedBulletLists();
|
||||
void testWriteNestedNumericLists();
|
||||
void testWriteTable();
|
||||
void rewriteDocument();
|
||||
void fromHtml_data();
|
||||
void fromHtml();
|
||||
|
||||
private:
|
||||
QString documentToUnixMarkdown();
|
||||
|
||||
private:
|
||||
QTextDocument *document;
|
||||
};
|
||||
|
||||
void tst_QTextMarkdownWriter::init()
|
||||
{
|
||||
document = new QTextDocument();
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::cleanup()
|
||||
{
|
||||
delete document;
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteParagraph_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("output");
|
||||
|
||||
QTest::newRow("empty") << "" <<
|
||||
"";
|
||||
QTest::newRow("spaces") << "foobar word" <<
|
||||
"foobar word\n\n";
|
||||
QTest::newRow("starting spaces") << " starting spaces" <<
|
||||
" starting spaces\n\n";
|
||||
QTest::newRow("trailing spaces") << "trailing spaces " <<
|
||||
"trailing spaces \n\n";
|
||||
QTest::newRow("tab") << "word\ttab x" <<
|
||||
"word\ttab x\n\n";
|
||||
QTest::newRow("tab2") << "word\t\ttab\tx" <<
|
||||
"word\t\ttab\tx\n\n";
|
||||
QTest::newRow("misc") << "foobar word\ttab x" <<
|
||||
"foobar word\ttab x\n\n";
|
||||
QTest::newRow("misc2") << "\t \tFoo" <<
|
||||
"\t \tFoo\n\n";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteParagraph()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, output);
|
||||
|
||||
QTextCursor cursor(document);
|
||||
cursor.insertText(input);
|
||||
|
||||
QCOMPARE(documentToUnixMarkdown(), output);
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteList()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
QTextList *list = cursor.createList(QTextListFormat::ListDisc);
|
||||
cursor.insertText("ListItem 1");
|
||||
list->add(cursor.block());
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 2");
|
||||
list->add(cursor.block());
|
||||
|
||||
QCOMPARE(documentToUnixMarkdown(), QString::fromLatin1(
|
||||
"- ListItem 1\n- ListItem 2\n"));
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteNestedBulletLists()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
|
||||
QTextList *list1 = cursor.createList(QTextListFormat::ListDisc);
|
||||
cursor.insertText("ListItem 1");
|
||||
list1->add(cursor.block());
|
||||
|
||||
QTextListFormat fmt2;
|
||||
fmt2.setStyle(QTextListFormat::ListCircle);
|
||||
fmt2.setIndent(2);
|
||||
QTextList *list2 = cursor.insertList(fmt2);
|
||||
cursor.insertText("ListItem 2");
|
||||
|
||||
QTextListFormat fmt3;
|
||||
fmt3.setStyle(QTextListFormat::ListSquare);
|
||||
fmt3.setIndent(3);
|
||||
cursor.insertList(fmt3);
|
||||
cursor.insertText("ListItem 3");
|
||||
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 4");
|
||||
list1->add(cursor.block());
|
||||
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 5");
|
||||
list2->add(cursor.block());
|
||||
|
||||
QCOMPARE(documentToUnixMarkdown(), QString::fromLatin1(
|
||||
"- ListItem 1\n * ListItem 2\n + ListItem 3\n- ListItem 4\n * ListItem 5\n"));
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
|
||||
QTextList *list1 = cursor.createList(QTextListFormat::ListDecimal);
|
||||
cursor.insertText("ListItem 1");
|
||||
list1->add(cursor.block());
|
||||
|
||||
QTextListFormat fmt2;
|
||||
fmt2.setStyle(QTextListFormat::ListLowerAlpha);
|
||||
fmt2.setNumberSuffix(QLatin1String(")"));
|
||||
fmt2.setIndent(2);
|
||||
QTextList *list2 = cursor.insertList(fmt2);
|
||||
cursor.insertText("ListItem 2");
|
||||
|
||||
QTextListFormat fmt3;
|
||||
fmt3.setStyle(QTextListFormat::ListDecimal);
|
||||
fmt3.setIndent(3);
|
||||
cursor.insertList(fmt3);
|
||||
cursor.insertText("ListItem 3");
|
||||
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 4");
|
||||
list1->add(cursor.block());
|
||||
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 5");
|
||||
list2->add(cursor.block());
|
||||
|
||||
// There's no QTextList API to set the starting number so we hard-coded all lists to start at 1 (QTBUG-65384)
|
||||
QCOMPARE(documentToUnixMarkdown(), QString::fromLatin1(
|
||||
"1 ListItem 1\n 1) ListItem 2\n 1 ListItem 3\n2 ListItem 4\n 2) ListItem 5\n"));
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteTable()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
QTextTable * table = cursor.insertTable(4, 3);
|
||||
cursor = table->cellAt(0, 0).firstCursorPosition();
|
||||
// valid Markdown tables need headers, but QTextTable doesn't make that distinction
|
||||
// so QTextMarkdownWriter assumes the first row of any table is a header
|
||||
cursor.insertText("one");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("two");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("three");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
|
||||
cursor.insertText("alice");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("bob");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("carl");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
|
||||
cursor.insertText("dennis");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("eric");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("fiona");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
|
||||
cursor.insertText("gina");
|
||||
/*
|
||||
|one |two |three|
|
||||
|------|----|-----|
|
||||
|alice |bob |carl |
|
||||
|dennis|eric|fiona|
|
||||
|gina | | |
|
||||
*/
|
||||
|
||||
QString md = documentToUnixMarkdown();
|
||||
|
||||
#ifdef DEBUG_WRITE_OUTPUT
|
||||
{
|
||||
QFile out("/tmp/table.md");
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(md.toUtf8());
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
QString expected = QString::fromLatin1(
|
||||
"\n|one |two |three|\n|------|----|-----|\n|alice |bob |carl |\n|dennis|eric|fiona|\n|gina | | |\n\n");
|
||||
QCOMPARE(md, expected);
|
||||
|
||||
// create table with merged cells
|
||||
document->clear();
|
||||
cursor = QTextCursor(document);
|
||||
table = cursor.insertTable(3, 3);
|
||||
table->mergeCells(0, 0, 1, 2);
|
||||
table->mergeCells(1, 1, 1, 2);
|
||||
cursor = table->cellAt(0, 0).firstCursorPosition();
|
||||
cursor.insertText("a");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("b");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("c");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("d");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("e");
|
||||
cursor.movePosition(QTextCursor::NextCell);
|
||||
cursor.insertText("f");
|
||||
/*
|
||||
+---+-+
|
||||
|a |b|
|
||||
+---+-+
|
||||
|c| d|
|
||||
+-+-+-+
|
||||
|e|f| |
|
||||
+-+-+-+
|
||||
|
||||
generates
|
||||
|
||||
|a ||b|
|
||||
|-|-|-|
|
||||
|c|d ||
|
||||
|e|f| |
|
||||
|
||||
*/
|
||||
|
||||
md = documentToUnixMarkdown();
|
||||
|
||||
#ifdef DEBUG_WRITE_OUTPUT
|
||||
{
|
||||
QFile out("/tmp/table-merged-cells.md");
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(md.toUtf8());
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
QCOMPARE(md, QString::fromLatin1("\n|a ||b|\n|-|-|-|\n|c|d ||\n|e|f| |\n\n"));
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::rewriteDocument()
|
||||
{
|
||||
QTextDocument doc;
|
||||
QFile f(QFINDTESTDATA("data/example.md"));
|
||||
QVERIFY(f.open(QFile::ReadOnly | QIODevice::Text));
|
||||
QString orig = QString::fromUtf8(f.readAll());
|
||||
f.close();
|
||||
doc.setMarkdown(orig);
|
||||
QString md = doc.toMarkdown();
|
||||
|
||||
#ifdef DEBUG_WRITE_OUTPUT
|
||||
QFile out("/tmp/rewrite.md");
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(md.toUtf8());
|
||||
out.close();
|
||||
#endif
|
||||
|
||||
QCOMPARE(md, orig);
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::fromHtml_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<QString>("output");
|
||||
|
||||
QTest::newRow("long URL") <<
|
||||
"<span style=\"font-style:italic;\">https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/</span>" <<
|
||||
"*https://www.example.com/dir/subdir/subsubdir/subsubsubdir/subsubsubsubdir/subsubsubsubsubdir/*\n\n";
|
||||
QTest::newRow("non-emphasis inline asterisk") << "3 * 4" << "3 * 4\n\n";
|
||||
QTest::newRow("arithmetic") << "(2 * a * x + b)^2 = b^2 - 4 * a * c" << "(2 * a * x + b)^2 = b^2 - 4 * a * c\n\n";
|
||||
QTest::newRow("escaped asterisk after newline") <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a star. * This is wrapped, but is <em>not</em> a bullet point." <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a star.\n\\* This is wrapped, but is *not* a bullet point.\n\n";
|
||||
QTest::newRow("escaped plus after newline") <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a plus. + This is wrapped, but is <em>not</em> a bullet point." <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a plus.\n\\+ This is wrapped, but is *not* a bullet point.\n\n";
|
||||
QTest::newRow("escaped hyphen after newline") <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a minus. - This is wrapped, but is <em>not</em> a bullet point." <<
|
||||
"The first sentence of this paragraph holds 80 characters, then there's a minus.\n\\- This is wrapped, but is *not* a bullet point.\n\n";
|
||||
// TODO
|
||||
// QTest::newRow("escaped number and paren after double newline") <<
|
||||
// "<p>(The first sentence of this paragraph is a line, the next paragraph has a number</p>13) but that's not part of an ordered list" <<
|
||||
// "(The first sentence of this paragraph is a line, the next paragraph has a number\n\n13\\) but that's not part of an ordered list\n\n";
|
||||
// QTest::newRow("preformats with embedded backticks") <<
|
||||
// "<pre>none `one` ``two``</pre><pre>```three``` ````four````</pre>plain" <<
|
||||
// "``` none `one` ``two`` ```\n\n````` ```three``` ````four```` `````\n\nplain\n\n";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::fromHtml()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(QString, output);
|
||||
|
||||
document->setHtml(input);
|
||||
QCOMPARE(documentToUnixMarkdown(), output);
|
||||
}
|
||||
|
||||
QString tst_QTextMarkdownWriter::documentToUnixMarkdown()
|
||||
{
|
||||
QString ret;
|
||||
QTextStream ts(&ret, QIODevice::WriteOnly);
|
||||
QTextMarkdownWriter writer(ts, QTextDocument::MarkdownDialectGitHub);
|
||||
writer.writeAll(*document);
|
||||
return ret;
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTextMarkdownWriter)
|
||||
#include "tst_qtextmarkdownwriter.moc"
|
@ -28,12 +28,15 @@ SUBDIRS=\
|
||||
|
||||
win32:SUBDIRS -= qtextpiecetable
|
||||
|
||||
qtConfig(textmarkdownwriter): SUBDIRS += qtextmarkdownwriter
|
||||
|
||||
!qtConfig(private_tests): SUBDIRS -= \
|
||||
qfontcache \
|
||||
qcssparser \
|
||||
qtextlayout \
|
||||
qtextpiecetable \
|
||||
qzip \
|
||||
qtextmarkdownwriter \
|
||||
qtextodfwriter
|
||||
|
||||
!qtHaveModule(xml): SUBDIRS -= \
|
||||
|
64
tests/manual/markdown/html2md.cpp
Normal file
64
tests/manual/markdown/html2md.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2019 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** 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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QGuiApplication>
|
||||
#include <QTextDocument>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
QGuiApplication::setApplicationVersion(QT_VERSION_STR);
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Converts the Qt-supported subset of HTML to Markdown.");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser.addPositionalArgument(QGuiApplication::translate("main", "input"),
|
||||
QGuiApplication::translate("main", "input file"));
|
||||
parser.addPositionalArgument(QGuiApplication::translate("main", "output"),
|
||||
QGuiApplication::translate("main", "output file"));
|
||||
parser.process(app);
|
||||
if (parser.positionalArguments().count() != 2)
|
||||
parser.showHelp(1);
|
||||
|
||||
QFile inFile(parser.positionalArguments().first());
|
||||
if (!inFile.open(QIODevice::ReadOnly)) {
|
||||
qFatal("failed to open %s for reading", parser.positionalArguments().first().toLocal8Bit().data());
|
||||
exit(2);
|
||||
}
|
||||
QFile outFile(parser.positionalArguments().at(1));
|
||||
if (!outFile.open(QIODevice::WriteOnly)) {
|
||||
qFatal("failed to open %s for writing", parser.positionalArguments().at(1).toLocal8Bit().data());
|
||||
exit(2);
|
||||
}
|
||||
QTextDocument doc;
|
||||
doc.setHtml(QString::fromUtf8(inFile.readAll()));
|
||||
outFile.write(doc.toMarkdown().toUtf8());
|
||||
}
|
6
tests/manual/markdown/html2md.pro
Normal file
6
tests/manual/markdown/html2md.pro
Normal file
@ -0,0 +1,6 @@
|
||||
TEMPLATE = app
|
||||
TARGET = html2md
|
||||
INCLUDEPATH += .
|
||||
#QT += gui-private
|
||||
SOURCES += html2md.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user