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",
|
"condition": "libs.libmd4c",
|
||||||
"output": [ "publicFeature" ]
|
"output": [ "publicFeature" ]
|
||||||
},
|
},
|
||||||
|
"textmarkdownwriter": {
|
||||||
|
"label": "MarkdownWriter",
|
||||||
|
"purpose": "Provides a Markdown (CommonMark) writer",
|
||||||
|
"section": "Kernel",
|
||||||
|
"output": [ "publicFeature" ]
|
||||||
|
},
|
||||||
"textodfwriter": {
|
"textodfwriter": {
|
||||||
"label": "OdfWriter",
|
"label": "OdfWriter",
|
||||||
"purpose": "Provides an ODF writer.",
|
"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",
|
"section": "Text formats",
|
||||||
"entries": [
|
"entries": [
|
||||||
"texthtmlparser", "cssparser", "textodfwriter", "textmarkdownreader", "system-textmarkdownreader"
|
"texthtmlparser", "cssparser", "textodfwriter", "textmarkdownreader", "system-textmarkdownreader", "textmarkdownwriter"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"egl",
|
"egl",
|
||||||
|
@ -73,6 +73,9 @@
|
|||||||
#if QT_CONFIG(textmarkdownreader)
|
#if QT_CONFIG(textmarkdownreader)
|
||||||
#include <private/qtextmarkdownimporter_p.h>
|
#include <private/qtextmarkdownimporter_p.h>
|
||||||
#endif
|
#endif
|
||||||
|
#if QT_CONFIG(textmarkdownwriter)
|
||||||
|
#include <private/qtextmarkdownwriter_p.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
@ -3288,6 +3291,22 @@ QString QTextDocument::toHtml(const QByteArray &encoding) const
|
|||||||
}
|
}
|
||||||
#endif // QT_NO_TEXTHTMLPARSER
|
#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
|
Replaces the entire contents of the document with the given
|
||||||
Markdown-formatted text in the \a markdown string, 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
|
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
|
way as in \l setHtml; however, Markdown formatting inside HTML blocks is
|
||||||
not supported. The \c MarkdownNoHTML feature flag can be set to disable
|
not supported.
|
||||||
HTML parsing.
|
|
||||||
|
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.
|
The undo/redo history is reset when this function is called.
|
||||||
*/
|
*/
|
||||||
|
@ -151,7 +151,7 @@ public:
|
|||||||
void setHtml(const QString &html);
|
void setHtml(const QString &html);
|
||||||
#endif
|
#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
|
// Must be in sync with QTextMarkdownImporter::Features, should be in sync with #define MD_FLAG_* in md4c
|
||||||
enum MarkdownFeature {
|
enum MarkdownFeature {
|
||||||
MarkdownNoHTML = 0x0020 | 0x0040,
|
MarkdownNoHTML = 0x0020 | 0x0040,
|
||||||
@ -160,7 +160,13 @@ public:
|
|||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(MarkdownFeatures, MarkdownFeature)
|
Q_DECLARE_FLAGS(MarkdownFeatures, MarkdownFeature)
|
||||||
Q_FLAG(MarkdownFeatures)
|
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);
|
void setMarkdown(const QString &markdown, MarkdownFeatures features = MarkdownDialectGitHub);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -51,6 +51,9 @@
|
|||||||
|
|
||||||
#include "qtextdocumentfragment_p.h"
|
#include "qtextdocumentfragment_p.h"
|
||||||
#include "qtextodfwriter_p.h"
|
#include "qtextodfwriter_p.h"
|
||||||
|
#if QT_CONFIG(textmarkdownwriter)
|
||||||
|
#include "qtextmarkdownwriter_p.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -267,6 +270,18 @@ bool QTextDocumentWriter::write(const QTextDocument *document)
|
|||||||
}
|
}
|
||||||
#endif // QT_NO_TEXTODFWRITER
|
#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
|
#ifndef QT_NO_TEXTHTMLPARSER
|
||||||
if (format == "html" || format == "htm") {
|
if (format == "html" || format == "htm") {
|
||||||
if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) {
|
if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) {
|
||||||
@ -348,6 +363,7 @@ QTextCodec *QTextDocumentWriter::codec() const
|
|||||||
\header \li Format \li Description
|
\header \li Format \li Description
|
||||||
\row \li plaintext \li Plain text
|
\row \li plaintext \li Plain text
|
||||||
\row \li HTML \li HyperText Markup Language
|
\row \li HTML \li HyperText Markup Language
|
||||||
|
\row \li markdown \li Markdown (CommonMark or GitHub dialects)
|
||||||
\row \li ODF \li OpenDocument Format
|
\row \li ODF \li OpenDocument Format
|
||||||
\endtable
|
\endtable
|
||||||
|
|
||||||
@ -364,6 +380,9 @@ QList<QByteArray> QTextDocumentWriter::supportedDocumentFormats()
|
|||||||
#ifndef QT_NO_TEXTODFWRITER
|
#ifndef QT_NO_TEXTODFWRITER
|
||||||
answer << "ODF";
|
answer << "ODF";
|
||||||
#endif // QT_NO_TEXTODFWRITER
|
#endif // QT_NO_TEXTODFWRITER
|
||||||
|
#if QT_CONFIG(textmarkdownwriter)
|
||||||
|
answer << "markdown";
|
||||||
|
#endif
|
||||||
|
|
||||||
std::sort(answer.begin(), answer.end());
|
std::sort(answer.begin(), answer.end());
|
||||||
return answer;
|
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(" + 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
|
text/qtextmarkdownimporter.cpp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qtConfig(textmarkdownwriter) {
|
||||||
|
HEADERS += \
|
||||||
|
text/qtextmarkdownwriter_p.h
|
||||||
|
SOURCES += \
|
||||||
|
text/qtextmarkdownwriter.cpp
|
||||||
|
}
|
||||||
|
|
||||||
qtConfig(cssparser) {
|
qtConfig(cssparser) {
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
text/qcssparser_p.h
|
text/qcssparser_p.h
|
||||||
|
@ -366,8 +366,8 @@ void QTextEditPrivate::_q_ensureVisible(const QRectF &_rect)
|
|||||||
\section1 Introduction and Concepts
|
\section1 Introduction and Concepts
|
||||||
|
|
||||||
QTextEdit is an advanced WYSIWYG viewer/editor supporting rich
|
QTextEdit is an advanced WYSIWYG viewer/editor supporting rich
|
||||||
text formatting using HTML-style tags. It is optimized to handle
|
text formatting using HTML-style tags, or Markdown format. It is optimized
|
||||||
large documents and to respond quickly to user input.
|
to handle large documents and to respond quickly to user input.
|
||||||
|
|
||||||
QTextEdit works on paragraphs and characters. A paragraph is a
|
QTextEdit works on paragraphs and characters. A paragraph is a
|
||||||
formatted string which is word-wrapped to fit into the width of
|
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
|
QTextEdit can display images, lists and tables. If the text is
|
||||||
too large to view within the text edit's viewport, scroll bars will
|
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.
|
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.
|
\l {Supported HTML Subset} page for more information.
|
||||||
|
|
||||||
If you just need to display a small piece of rich text use QLabel.
|
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
|
QTextEdit can display a large HTML subset, including tables and
|
||||||
images.
|
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
|
existing text and replaces it with the text passed in the
|
||||||
setHtml() call. If you call setHtml() with legacy HTML, and then
|
setHtml() call. If you call setHtml() with legacy HTML, and then
|
||||||
call toHtml(), the text that is returned may have different markup,
|
call toHtml(), the text that is returned may have different markup,
|
||||||
but will render the same. The entire text can be deleted with clear().
|
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
|
Text itself can be inserted using the QTextCursor class or using the
|
||||||
convenience functions insertHtml(), insertPlainText(), append() or
|
convenience functions insertHtml(), insertPlainText(), append() or
|
||||||
paste(). QTextCursor is also able to insert complex objects like tables
|
paste(). QTextCursor is also able to insert complex objects like tables
|
||||||
@ -1213,11 +1220,54 @@ QString QTextEdit::toHtml() const
|
|||||||
}
|
}
|
||||||
#endif
|
#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)
|
#if QT_CONFIG(textmarkdownreader)
|
||||||
void QTextEdit::setMarkdown(const QString &text)
|
void QTextEdit::setMarkdown(const QString &markdown)
|
||||||
{
|
{
|
||||||
Q_D(const QTextEdit);
|
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
|
#endif
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ class Q_WIDGETS_EXPORT QTextEdit : public QAbstractScrollArea
|
|||||||
QDOC_PROPERTY(QTextOption::WrapMode wordWrapMode READ wordWrapMode WRITE setWordWrapMode)
|
QDOC_PROPERTY(QTextOption::WrapMode wordWrapMode READ wordWrapMode WRITE setWordWrapMode)
|
||||||
Q_PROPERTY(int lineWrapColumnOrWidth READ lineWrapColumnOrWidth WRITE setLineWrapColumnOrWidth)
|
Q_PROPERTY(int lineWrapColumnOrWidth READ lineWrapColumnOrWidth WRITE setLineWrapColumnOrWidth)
|
||||||
Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly)
|
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
|
#ifndef QT_NO_TEXTHTMLPARSER
|
||||||
Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true)
|
Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true)
|
||||||
#endif
|
#endif
|
||||||
@ -174,6 +177,9 @@ public:
|
|||||||
#ifndef QT_NO_TEXTHTMLPARSER
|
#ifndef QT_NO_TEXTHTMLPARSER
|
||||||
QString toHtml() const;
|
QString toHtml() const;
|
||||||
#endif
|
#endif
|
||||||
|
#if QT_CONFIG(textmarkdownwriter)
|
||||||
|
QString toMarkdown(QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
void ensureCursorVisible();
|
void ensureCursorVisible();
|
||||||
|
|
||||||
@ -239,7 +245,7 @@ public Q_SLOTS:
|
|||||||
void setHtml(const QString &text);
|
void setHtml(const QString &text);
|
||||||
#endif
|
#endif
|
||||||
#if QT_CONFIG(textmarkdownreader)
|
#if QT_CONFIG(textmarkdownreader)
|
||||||
void setMarkdown(const QString &text);
|
void setMarkdown(const QString &markdown);
|
||||||
#endif
|
#endif
|
||||||
void setText(const QString &text);
|
void setText(const QString &text);
|
||||||
|
|
||||||
|
@ -3130,6 +3130,13 @@ QString QWidgetTextControl::toHtml() const
|
|||||||
}
|
}
|
||||||
#endif
|
#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)
|
void QWidgetTextControlPrivate::append(const QString &text, Qt::TextFormat format)
|
||||||
{
|
{
|
||||||
QTextCursor tmp(doc);
|
QTextCursor tmp(doc);
|
||||||
|
@ -128,6 +128,9 @@ public:
|
|||||||
#ifndef QT_NO_TEXTHTMLPARSER
|
#ifndef QT_NO_TEXTHTMLPARSER
|
||||||
QString toHtml() const;
|
QString toHtml() const;
|
||||||
#endif
|
#endif
|
||||||
|
#if QT_CONFIG(textmarkdownwriter)
|
||||||
|
QString toMarkdown(QTextDocument::MarkdownFeatures features = QTextDocument::MarkdownDialectGitHub) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual void ensureCursorVisible();
|
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.
|
||||||
|
|
||||||
|
 *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
|
win32:SUBDIRS -= qtextpiecetable
|
||||||
|
|
||||||
|
qtConfig(textmarkdownwriter): SUBDIRS += qtextmarkdownwriter
|
||||||
|
|
||||||
!qtConfig(private_tests): SUBDIRS -= \
|
!qtConfig(private_tests): SUBDIRS -= \
|
||||||
qfontcache \
|
qfontcache \
|
||||||
qcssparser \
|
qcssparser \
|
||||||
qtextlayout \
|
qtextlayout \
|
||||||
qtextpiecetable \
|
qtextpiecetable \
|
||||||
qzip \
|
qzip \
|
||||||
|
qtextmarkdownwriter \
|
||||||
qtextodfwriter
|
qtextodfwriter
|
||||||
|
|
||||||
!qtHaveModule(xml): SUBDIRS -= \
|
!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…
x
Reference in New Issue
Block a user