Add QTextListFormat::start: html and markdown ordered list index offset
This is useful for a lot of applications that render text coming from sources which already support arbitrarily numbered lists (like chat applications for example). Application-side workarounds usually have significant overhead; and this feature has been requested multiple times. It should be possible to both read and write HTML with the <ol start="x"> attribute, and read and write Markdown with arbitrary numbers for the first item in a list. [ChangeLog][QtGui] QTextList now supports specifying a start index using the new QTextList::{setStart, start} functions. The HTML start attribute on ordered lists in rich text documents is now parsed and used when rendering a text list. Non-negative indices in markdown lists are now also parsed and written properly. This allows starting a list with a different number than 1. Fixes: QTBUG-30407 Fixes: QTBUG-65384 Task-number: QTBUG-107562 Change-Id: Ib35b9378d9134ffedaa2d92f728b0984793aa7c1 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
parent
b05540bd00
commit
3f40a8b5b1
@ -2996,18 +2996,25 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block)
|
||||
if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
|
||||
const QTextListFormat format = list->format();
|
||||
const int style = format.style();
|
||||
bool ordered = false;
|
||||
switch (style) {
|
||||
case QTextListFormat::ListDecimal: html += "<ol"_L1; break;
|
||||
case QTextListFormat::ListDisc: html += "<ul"_L1; break;
|
||||
case QTextListFormat::ListCircle: html += "<ul type=\"circle\""_L1; break;
|
||||
case QTextListFormat::ListSquare: html += "<ul type=\"square\""_L1; break;
|
||||
case QTextListFormat::ListLowerAlpha: html += "<ol type=\"a\""_L1; break;
|
||||
case QTextListFormat::ListUpperAlpha: html += "<ol type=\"A\""_L1; break;
|
||||
case QTextListFormat::ListLowerRoman: html += "<ol type=\"i\""_L1; break;
|
||||
case QTextListFormat::ListUpperRoman: html += "<ol type=\"I\""_L1; break;
|
||||
case QTextListFormat::ListDecimal: html += "<ol"_L1; ordered = true; break;
|
||||
case QTextListFormat::ListLowerAlpha: html += "<ol type=\"a\""_L1; ordered = true; break;
|
||||
case QTextListFormat::ListUpperAlpha: html += "<ol type=\"A\""_L1; ordered = true; break;
|
||||
case QTextListFormat::ListLowerRoman: html += "<ol type=\"i\""_L1; ordered = true; break;
|
||||
case QTextListFormat::ListUpperRoman: html += "<ol type=\"I\""_L1; ordered = true; break;
|
||||
default: html += "<ul"_L1; // ### should not happen
|
||||
}
|
||||
|
||||
if (ordered && format.start() != 1) {
|
||||
html += " start=\""_L1;
|
||||
html += QString::number(format.start());
|
||||
html += u'"';
|
||||
}
|
||||
|
||||
QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
|
||||
|
||||
if (format.hasProperty(QTextFormat::ListIndent)) {
|
||||
|
@ -699,6 +699,8 @@ QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
|
||||
listFmt.setNumberPrefix(currentNode->textListNumberPrefix);
|
||||
if (!currentNode->textListNumberSuffix.isNull())
|
||||
listFmt.setNumberSuffix(currentNode->textListNumberSuffix);
|
||||
if (currentNode->listStart != 1)
|
||||
listFmt.setStart(currentNode->listStart);
|
||||
|
||||
++indent;
|
||||
if (currentNode->hasCssListIndent)
|
||||
|
@ -2610,7 +2610,8 @@ QList<QTextOption::Tab> QTextBlockFormat::tabPositions() const
|
||||
The style used to decorate each item is set with setStyle() and can be read
|
||||
with the style() function. The style controls the type of bullet points and
|
||||
numbering scheme used for items in the list. Note that lists that use the
|
||||
decimal numbering scheme begin counting at 1 rather than 0.
|
||||
decimal numbering scheme begin counting at 1 rather than 0, unless it has
|
||||
been overridden via setStart().
|
||||
|
||||
Style properties can be set to further configure the appearance of list
|
||||
items; for example, the ListNumberPrefix and ListNumberSuffix properties
|
||||
@ -2647,6 +2648,7 @@ QTextListFormat::QTextListFormat()
|
||||
: QTextFormat(ListFormat)
|
||||
{
|
||||
setIndent(1);
|
||||
setStart(1);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -2750,6 +2752,32 @@ QTextListFormat::QTextListFormat(const QTextFormat &fmt)
|
||||
\sa setNumberSuffix()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn void QTextListFormat::setStart(int start)
|
||||
\since 6.6
|
||||
|
||||
Sets the list format's \a start index.
|
||||
|
||||
This allows you to start a list with an index other than 1. This can be
|
||||
used with all sorted list types: for example if the style() is
|
||||
QTextListFormat::ListLowerAlpha and start() is \c 4, the first list item
|
||||
begins with "d". It does not have any effect on unsorted list types.
|
||||
|
||||
The default start is \c 1.
|
||||
|
||||
\sa start()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn int QTextListFormat::start() const
|
||||
\since 6.6
|
||||
|
||||
Returns the number to be shown by the first list item, if the style() is
|
||||
QTextListFormat::ListDecimal, or to offset other sorted list types.
|
||||
|
||||
\sa setStart()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QTextFrameFormat
|
||||
\reentrant
|
||||
|
@ -188,6 +188,7 @@ public:
|
||||
ListIndent = 0x3001,
|
||||
ListNumberPrefix = 0x3002,
|
||||
ListNumberSuffix = 0x3003,
|
||||
ListStart = 0x3004,
|
||||
|
||||
// table and frame properties
|
||||
FrameBorder = 0x4000,
|
||||
@ -753,6 +754,9 @@ public:
|
||||
inline QString numberSuffix() const
|
||||
{ return stringProperty(ListNumberSuffix); }
|
||||
|
||||
inline void setStart(int indent);
|
||||
inline int start() const { return intProperty(ListStart); }
|
||||
|
||||
protected:
|
||||
explicit QTextListFormat(const QTextFormat &fmt);
|
||||
friend class QTextFormat;
|
||||
@ -772,6 +776,11 @@ inline void QTextListFormat::setNumberPrefix(const QString &np)
|
||||
inline void QTextListFormat::setNumberSuffix(const QString &ns)
|
||||
{ setProperty(ListNumberSuffix, ns); }
|
||||
|
||||
inline void QTextListFormat::setStart(int astart)
|
||||
{
|
||||
setProperty(ListStart, astart);
|
||||
}
|
||||
|
||||
class Q_GUI_EXPORT QTextImageFormat : public QTextCharFormat
|
||||
{
|
||||
public:
|
||||
|
@ -1634,6 +1634,8 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
|
||||
else if (value == "none"_L1)
|
||||
node->listStyle = QTextListFormat::ListStyleUndefined;
|
||||
}
|
||||
} else if (key == "start"_L1) {
|
||||
setIntAttribute(&node->listStart, value);
|
||||
}
|
||||
break;
|
||||
case Html_li:
|
||||
|
@ -150,6 +150,7 @@ struct QTextHtmlParserNode {
|
||||
uint displayMode : 3; // QTextHtmlElement::DisplayMode
|
||||
uint hasHref : 1;
|
||||
QTextListFormat::Style listStyle;
|
||||
int listStart = 1;
|
||||
QString textListNumberPrefix;
|
||||
QString textListNumberSuffix;
|
||||
QString imageName;
|
||||
|
@ -147,6 +147,9 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
|
||||
QString numberPrefix;
|
||||
QString numberSuffix = u"."_s;
|
||||
|
||||
// the number of the item might be offset by start, which defaults to 1
|
||||
const int itemNumber = item + format().start() - 1;
|
||||
|
||||
if (format().hasProperty(QTextFormat::ListNumberPrefix))
|
||||
numberPrefix = format().numberPrefix();
|
||||
if (format().hasProperty(QTextFormat::ListNumberSuffix))
|
||||
@ -154,15 +157,21 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
|
||||
|
||||
switch (style) {
|
||||
case QTextListFormat::ListDecimal:
|
||||
result = QString::number(item);
|
||||
result = QString::number(itemNumber);
|
||||
break;
|
||||
// from the old richtext
|
||||
case QTextListFormat::ListLowerAlpha:
|
||||
case QTextListFormat::ListUpperAlpha:
|
||||
{
|
||||
// match the html default behavior of falling back to decimal numbers
|
||||
if (itemNumber < 1) {
|
||||
result = QString::number(itemNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
const char baseChar = style == QTextListFormat::ListUpperAlpha ? 'A' : 'a';
|
||||
|
||||
int c = item;
|
||||
int c = itemNumber;
|
||||
while (c > 0) {
|
||||
c--;
|
||||
result.prepend(QChar::fromUcs2(baseChar + (c % 26)));
|
||||
@ -173,7 +182,10 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
|
||||
case QTextListFormat::ListLowerRoman:
|
||||
case QTextListFormat::ListUpperRoman:
|
||||
{
|
||||
if (item < 5000) {
|
||||
// match the html default behavior of falling back to decimal numbers
|
||||
if (itemNumber < 1) {
|
||||
result = QString::number(itemNumber);
|
||||
} else if (itemNumber < 5000) {
|
||||
QByteArray romanNumeral;
|
||||
|
||||
// works for up to 4999 items
|
||||
@ -186,7 +198,7 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
|
||||
romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
|
||||
|
||||
int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
|
||||
int n = item;
|
||||
int n = itemNumber;
|
||||
for (int i = 12; i >= 0; n %= c[i], i--) {
|
||||
int q = n / c[i];
|
||||
if (q > 0) {
|
||||
@ -212,8 +224,7 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
|
||||
}
|
||||
}
|
||||
result = QString::fromLatin1(romanNumeral);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result = u"?"_s;
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,8 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
|
||||
m_listFormat.setIndent(m_listStack.size() + 1);
|
||||
m_listFormat.setNumberSuffix(QChar::fromLatin1(detail->mark_delimiter));
|
||||
m_listFormat.setStyle(QTextListFormat::ListDecimal);
|
||||
qCDebug(lcMD, "OL xx%d level %d", detail->mark_delimiter, int(m_listStack.size()) + 1);
|
||||
m_listFormat.setStart(detail->start);
|
||||
qCDebug(lcMD, "OL xx%d level %d start %d", detail->mark_delimiter, int(m_listStack.size()) + 1, detail->start);
|
||||
} break;
|
||||
case MD_BLOCK_TD: {
|
||||
MD_BLOCK_TD_DETAIL *detail = static_cast<MD_BLOCK_TD_DETAIL *>(det);
|
||||
|
@ -344,7 +344,9 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
|
||||
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;
|
||||
// Negative numbers don't start a list in Markdown, so ignore them.
|
||||
const int start = fmt.start() >= 0 ? fmt.start() : 1;
|
||||
const int number = block.textList()->itemNumber(block) + start;
|
||||
QByteArray bullet = " ";
|
||||
bool numeric = false;
|
||||
switch (fmt.style()) {
|
||||
|
@ -1545,15 +1545,20 @@ void tst_QTextCursor::insertMarkdown_data()
|
||||
<< 6 << QString("0) eggs\n1) maple syrup\n")
|
||||
<< QString("bread\u2029eggs\u2029maple syrup\u2029milk")
|
||||
<< QString("bread\neggs\nmaple syrup\nmilk")
|
||||
<< QString("1) bread\n2) eggs\n1) maple syrup\n2) milk\n");
|
||||
// renumbering would happen if we re-read the whole document
|
||||
<< QString("1) bread\n2) eggs\n0) maple syrup\n1) milk\n");
|
||||
// Renumbering would happen if we re-read the whole document.
|
||||
// Currently insertion only uses the new list format after a paragraph separator.
|
||||
// For that reason "bread" and "eggs" use the original list format, while "maple syrup" and
|
||||
// "milk" use the format from the just inserted list.
|
||||
|
||||
QTest::newRow("list after a list")
|
||||
<< "1) bread\n2) milk\n\n" << 2
|
||||
<< 13 << QString("\n0) eggs\n1) maple syrup\n")
|
||||
<< QString("bread\u2029milk\u2029eggs\u2029maple syrup")
|
||||
<< QString("bread\nmilk\neggs\nmaple syrup")
|
||||
<< QString("1) bread\n2) milk\n3) eggs\n1) maple syrup\n");
|
||||
<< QString("1) bread\n2) milk\n3) eggs\n0) maple syrup\n");
|
||||
// Same behavior as above. "eggs" uses the original list format, but "maple syrup" uses the
|
||||
// format of the inserted list, which means "maple syrup" now has a start of 0.
|
||||
|
||||
const QString markdownHeadingString("# Hello\nWorld\n");
|
||||
|
||||
|
@ -1780,6 +1780,22 @@ void tst_QTextDocument::toHtml_data()
|
||||
"<li class=\"unchecked\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">unchecked item</li>\n"
|
||||
"<li class=\"checked\" style=\" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">checked item</li></ul>");
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_DOC_AND_CURSOR();
|
||||
|
||||
QTextListFormat fmt;
|
||||
fmt.setStyle(QTextListFormat::ListDecimal);
|
||||
fmt.setStart(4);
|
||||
cursor.insertList(fmt);
|
||||
cursor.insertText("Blah");
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("Bleh");
|
||||
|
||||
QTest::newRow("ordered list with start") << QTextDocumentFragment(&doc)
|
||||
<< QString("EMPTYBLOCK") +
|
||||
QString("<ol start=\"4\" style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\">\n<li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</li>\n<li style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Bleh</li></ol>");
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QTextDocument::toHtml()
|
||||
|
@ -96,6 +96,7 @@ private slots:
|
||||
void html_thCentered();
|
||||
void orderedListNumbering();
|
||||
void html_blockAfterList();
|
||||
void html_listStartAttribute();
|
||||
void html_subAndSuperScript();
|
||||
void html_cssColors();
|
||||
void obeyFragmentMarkersInImport();
|
||||
@ -1471,6 +1472,22 @@ void tst_QTextDocumentFragment::html_blockAfterList()
|
||||
QCOMPARE(cursor.blockFormat().indent(), 0);
|
||||
}
|
||||
|
||||
void tst_QTextDocumentFragment::html_listStartAttribute()
|
||||
{
|
||||
const char html[] = "<ol start=-1><li>Foo</ol><ol><li>Bar</ol>";
|
||||
cursor.insertFragment(QTextDocumentFragment::fromHtml(html));
|
||||
|
||||
cursor.movePosition(QTextCursor::Start);
|
||||
|
||||
QVERIFY(cursor.currentList());
|
||||
QCOMPARE(cursor.currentList()->format().start(), -1);
|
||||
|
||||
QVERIFY(cursor.movePosition(QTextCursor::NextBlock));
|
||||
|
||||
QVERIFY(cursor.currentList());
|
||||
QCOMPARE(cursor.currentList()->format().start(), 1);
|
||||
}
|
||||
|
||||
void tst_QTextDocumentFragment::html_subAndSuperScript()
|
||||
{
|
||||
const char subHtml[] = "<sub>Subby</sub>";
|
||||
|
@ -36,6 +36,8 @@ private slots:
|
||||
void blockUpdate();
|
||||
void numbering_data();
|
||||
void numbering();
|
||||
void start_data();
|
||||
void start();
|
||||
|
||||
private:
|
||||
QTextDocument *doc;
|
||||
@ -400,5 +402,61 @@ void tst_QTextList::numbering()
|
||||
QCOMPARE(cursor.currentList()->itemText(cursor.block()), result);
|
||||
}
|
||||
|
||||
void tst_QTextList::start_data()
|
||||
{
|
||||
QTest::addColumn<int>("format");
|
||||
QTest::addColumn<int>("start");
|
||||
QTest::addColumn<QStringList>("expectedItemTexts");
|
||||
|
||||
QTest::newRow("-1.") << int(QTextListFormat::ListDecimal) << -1
|
||||
<< QStringList{ "-1.", "0.", "1." };
|
||||
QTest::newRow("0.") << int(QTextListFormat::ListDecimal) << 0
|
||||
<< QStringList{ "0.", "1.", "2." };
|
||||
QTest::newRow("1.") << int(QTextListFormat::ListDecimal) << 1
|
||||
<< QStringList{ "1.", "2.", "3." };
|
||||
|
||||
QTest::newRow("A. -1") << int(QTextListFormat::ListUpperAlpha) << -1
|
||||
<< QStringList{ "-1.", "0.", "A." };
|
||||
QTest::newRow("A. 0.") << int(QTextListFormat::ListUpperAlpha) << 0
|
||||
<< QStringList{ "0.", "A.", "B." };
|
||||
QTest::newRow("a. -1") << int(QTextListFormat::ListLowerAlpha) << -1
|
||||
<< QStringList{ "-1.", "0.", "a." };
|
||||
QTest::newRow("a. 0.") << int(QTextListFormat::ListLowerAlpha) << 0
|
||||
<< QStringList{ "0.", "a.", "b." };
|
||||
QTest::newRow("d. 4.") << int(QTextListFormat::ListLowerAlpha) << 4
|
||||
<< QStringList{ "d.", "e.", "f." };
|
||||
|
||||
QTest::newRow("I. -1") << int(QTextListFormat::ListUpperRoman) << -1
|
||||
<< QStringList{ "-1.", "0.", "I." };
|
||||
QTest::newRow("I. 0.") << int(QTextListFormat::ListUpperRoman) << 0
|
||||
<< QStringList{ "0.", "I.", "II." };
|
||||
QTest::newRow("i. -1") << int(QTextListFormat::ListLowerRoman) << -1
|
||||
<< QStringList{ "-1.", "0.", "i." };
|
||||
QTest::newRow("i. 0.") << int(QTextListFormat::ListLowerRoman) << 0
|
||||
<< QStringList{ "0.", "i.", "ii." };
|
||||
}
|
||||
|
||||
void tst_QTextList::start()
|
||||
{
|
||||
QFETCH(int, format);
|
||||
QFETCH(int, start);
|
||||
QFETCH(QStringList, expectedItemTexts);
|
||||
|
||||
QTextListFormat fmt;
|
||||
fmt.setStyle(QTextListFormat::Style(format));
|
||||
fmt.setStart(start);
|
||||
QTextList *list = cursor.createList(fmt);
|
||||
QVERIFY(list);
|
||||
|
||||
while (list->count() < int(expectedItemTexts.size()))
|
||||
cursor.insertBlock();
|
||||
|
||||
QCOMPARE(list->count(), expectedItemTexts.size());
|
||||
|
||||
for (int i = 0; i < list->count(); ++i)
|
||||
QCOMPARE(cursor.currentList()->itemText(cursor.currentList()->item(i)),
|
||||
expectedItemTexts[i]);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTextList)
|
||||
#include "tst_qtextlist.moc"
|
||||
|
@ -182,36 +182,58 @@ void tst_QTextMarkdownImporter::thematicBreaks()
|
||||
void tst_QTextMarkdownImporter::lists_data()
|
||||
{
|
||||
QTest::addColumn<QString>("input");
|
||||
QTest::addColumn<int>("skipToCheckStart");
|
||||
QTest::addColumn<int>("expectedListStart");
|
||||
QTest::addColumn<int>("expectedItemCount");
|
||||
QTest::addColumn<bool>("expectedEmptyItems");
|
||||
QTest::addColumn<QString>("rewrite");
|
||||
|
||||
// Some of these cases show odd behavior, which is subject to change
|
||||
// as the importer and the writer are tweaked to fix bugs over time.
|
||||
QTest::newRow("dot newline") << ".\n" << 0 << true << ".\n\n";
|
||||
QTest::newRow("number dot newline") << "1.\n" << 1 << true << "1. \n";
|
||||
QTest::newRow("star newline") << "*\n" << 1 << true << "* \n";
|
||||
QTest::newRow("hyphen newline") << "-\n" << 1 << true << "- \n";
|
||||
QTest::newRow("hyphen space newline") << "- \n" << 1 << true << "- \n";
|
||||
QTest::newRow("hyphen space letter newline") << "- a\n" << 1 << false << "- a\n";
|
||||
QTest::newRow("dot newline") << ".\n" << 0 << 1 << 0 << true << ".\n\n";
|
||||
QTest::newRow("number dot newline") << "1.\n" << 0 << 1 << 1 << true << "1. \n";
|
||||
QTest::newRow("number offset start") << "2. text\n" << 0 << 2 << 1 << false << "2. text\n";
|
||||
QTest::newRow("second list offset start")
|
||||
<< "1. text\n\nintervening paragraph\n\n4. second list item"
|
||||
<< 2 << 4 << 2 << false
|
||||
<< "1. text\n\nintervening paragraph\n\n4. second list item\n";
|
||||
QTest::newRow("list continuation offset start")
|
||||
<< "3. text\n\n next paragraph in item 1\n10. second list item"
|
||||
<< 2 << 3 << 2 << false
|
||||
<< "3. text\n\n next paragraph in item 1\n\n4. second list item\n";
|
||||
QTest::newRow("nested list offset start")
|
||||
<< "1. text\n\n 0. indented list item\n\n4. second item in first list"
|
||||
<< 1 << 0 << 3 << false
|
||||
<< "1. text\n 0. indented list item\n2. second item in first list\n";
|
||||
QTest::newRow("offset start after nested list")
|
||||
<< "1. text\n\n 0. indented list item\n\n4. second item in first list"
|
||||
<< 2 << 1 << 3 << false
|
||||
<< "1. text\n 0. indented list item\n2. second item in first list\n";
|
||||
QTest::newRow("star newline") << "*\n" << 0 << 1 << 1 << true << "* \n";
|
||||
QTest::newRow("hyphen newline") << "-\n" << 0 << 1 << 1 << true << "- \n";
|
||||
QTest::newRow("hyphen space newline") << "- \n" << 0 << 1 << 1 << true << "- \n";
|
||||
QTest::newRow("hyphen space letter newline") << "- a\n" << 0 << 1 << 1 << false << "- a\n";
|
||||
QTest::newRow("hyphen nbsp newline") <<
|
||||
QString::fromUtf8("-\u00A0\n") << 0 << true << "-\u00A0\n\n";
|
||||
QTest::newRow("nested empty lists") << "*\n *\n *\n" << 1 << true << " * \n";
|
||||
QTest::newRow("list nested in empty list") << "-\n * a\n" << 2 << false << "- \n * a\n";
|
||||
QString::fromUtf8("-\u00A0\n") << 0 << 1 << 0 << true << "-\u00A0\n\n";
|
||||
QTest::newRow("nested empty lists") << "*\n *\n *\n" << 0 << 1 << 1 << true << " * \n";
|
||||
QTest::newRow("list nested in empty list") << "-\n * a\n" << 0 << 1 << 2 << false << "- \n * a\n";
|
||||
QTest::newRow("lists nested in empty lists")
|
||||
<< "-\n * a\n * b\n- c\n *\n + d\n" << 5 << false
|
||||
<< "-\n * a\n * b\n- c\n *\n + d\n" << 0 << 1 << 5 << false
|
||||
<< "- \n * a\n * b\n- c *\n + d\n";
|
||||
QTest::newRow("numeric lists nested in empty lists")
|
||||
<< "- \n 1. a\n 2. b\n- c\n 1.\n + d\n" << 4 << false
|
||||
<< "- \n 1. a\n 2. b\n- c\n 1.\n + d\n" << 0 << 1 << 4 << false
|
||||
<< "- \n 1. a\n 2. b\n- c 1. + d\n";
|
||||
QTest::newRow("styled spans in list items")
|
||||
<< "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n" << 5 << false
|
||||
<< "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n"
|
||||
<< 0 << 1 << 5 << false
|
||||
<< "1. normal text\n2. **bold** text\n3. `code` in the item\n4. *italic* text\n5. _underlined_ text\n";
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownImporter::lists()
|
||||
{
|
||||
QFETCH(QString, input);
|
||||
QFETCH(int, skipToCheckStart);
|
||||
QFETCH(int, expectedListStart);
|
||||
QFETCH(int, expectedItemCount);
|
||||
QFETCH(bool, expectedEmptyItems);
|
||||
QFETCH(QString, rewrite);
|
||||
@ -227,6 +249,8 @@ void tst_QTextMarkdownImporter::lists()
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
qCDebug(lcTests) << " original:" << input;
|
||||
qCDebug(lcTests) << "rewritten:" << doc.toMarkdown();
|
||||
|
||||
QTextFrame::iterator iterator = doc.rootFrame()->begin();
|
||||
QTextFrame *currentFrame = iterator.currentFrame();
|
||||
@ -239,10 +263,12 @@ void tst_QTextMarkdownImporter::lists()
|
||||
QCOMPARE(iterator.currentFrame(), currentFrame);
|
||||
// Check whether the block is text or a horizontal rule
|
||||
QTextBlock block = iterator.currentBlock();
|
||||
QTextListFormat listFmt;
|
||||
if (block.textList()) {
|
||||
++itemCount;
|
||||
if (!block.text().isEmpty())
|
||||
emptyItems = false;
|
||||
listFmt = block.textList()->format();
|
||||
}
|
||||
qCDebug(lcTests, "%d %s%s", i,
|
||||
(block.textList() ? "<li>" : "<p>"), qPrintable(block.text()));
|
||||
@ -261,6 +287,11 @@ void tst_QTextMarkdownImporter::lists()
|
||||
QCOMPARE(listItemFmt.fontItalic(), false);
|
||||
QCOMPARE(listItemFmt.fontUnderline(), false);
|
||||
QCOMPARE(listItemFmt.fontFixedPitch(), false);
|
||||
if (i == skipToCheckStart) {
|
||||
qCDebug(lcTests) << "skipped to list item" << i << block.text()
|
||||
<< "start" << listFmt.start() << "expected" << expectedListStart;
|
||||
QCOMPARE(listFmt.start(), expectedListStart);
|
||||
}
|
||||
++iterator;
|
||||
++i;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ private slots:
|
||||
void testWriteNestedBulletLists_data();
|
||||
void testWriteNestedBulletLists();
|
||||
void testWriteNestedNumericLists();
|
||||
void testWriteNumericListWithStart();
|
||||
void testWriteTable();
|
||||
void rewriteDocument_data();
|
||||
void rewriteDocument();
|
||||
@ -284,6 +285,7 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
|
||||
list1->add(cursor.block());
|
||||
|
||||
QTextListFormat fmt2;
|
||||
// Alpha "numbering" is not supported in markdown, so we'll actually get decimal.
|
||||
fmt2.setStyle(QTextListFormat::ListLowerAlpha);
|
||||
fmt2.setNumberSuffix(QLatin1String(")"));
|
||||
fmt2.setIndent(2);
|
||||
@ -305,7 +307,24 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
|
||||
list2->add(cursor.block());
|
||||
|
||||
const QString output = documentToUnixMarkdown();
|
||||
// There's no QTextList API to set the starting number so we hard-coded all lists to start at 1 (QTBUG-65384)
|
||||
|
||||
#ifdef DEBUG_WRITE_OUTPUT
|
||||
{
|
||||
QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".md"));
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(output.toUtf8());
|
||||
out.close();
|
||||
}
|
||||
{
|
||||
QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".html"));
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(document->toHtml().toUtf8());
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// While we can set the start index for a block, if list items intersect each other, they will
|
||||
// still use the list numbering.
|
||||
const QString expected = QString::fromLatin1(
|
||||
"1. ListItem 1\n 1) ListItem 2\n 1. ListItem 3\n2. ListItem 4\n 2) ListItem 5\n");
|
||||
if (output != expected && isMainFontFixed())
|
||||
@ -313,6 +332,92 @@ void tst_QTextMarkdownWriter::testWriteNestedNumericLists()
|
||||
QCOMPARE(output, expected);
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteNumericListWithStart()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
|
||||
// The first list will start at 2.
|
||||
QTextListFormat fmt1;
|
||||
fmt1.setStyle(QTextListFormat::ListDecimal);
|
||||
fmt1.setStart(2);
|
||||
QTextList *list1 = cursor.createList(fmt1);
|
||||
cursor.insertText("ListItem 1");
|
||||
list1->add(cursor.block());
|
||||
|
||||
// This list uses the default start (1) again.
|
||||
QTextListFormat fmt2;
|
||||
// Alpha "numbering" is not supported in markdown, so we'll actually get decimal.
|
||||
fmt2.setStyle(QTextListFormat::ListLowerAlpha);
|
||||
fmt2.setNumberSuffix(QLatin1String(")"));
|
||||
fmt2.setIndent(2);
|
||||
QTextList *list2 = cursor.insertList(fmt2);
|
||||
cursor.insertText("ListItem 2");
|
||||
|
||||
// Negative list numbers are disallowed by most Markdown implementations. This list will start
|
||||
// at 1 for that reason.
|
||||
QTextListFormat fmt3;
|
||||
fmt3.setStyle(QTextListFormat::ListDecimal);
|
||||
fmt3.setIndent(3);
|
||||
fmt3.setStart(-1);
|
||||
cursor.insertList(fmt3);
|
||||
cursor.insertText("ListItem 3");
|
||||
|
||||
// Continuing list1, so the second item will have the number 3.
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 4");
|
||||
list1->add(cursor.block());
|
||||
|
||||
// This will look out of place: it's in a different position than its list would suggest.
|
||||
// Generates invalid markdown numbering (OK for humans, but md4c will parse it differently than we "meant").
|
||||
// TODO QTBUG-111707: the writer needs to add newlines, otherwise ListItem 5 becomes part of the text for ListItem 4.
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("ListItem 5");
|
||||
list2->add(cursor.block());
|
||||
|
||||
// 0 indexed lists are fine.
|
||||
QTextListFormat fmt4;
|
||||
fmt4.setStyle(QTextListFormat::ListDecimal);
|
||||
fmt4.setStart(0);
|
||||
QTextList *list4 = cursor.insertList(fmt4);
|
||||
cursor.insertText("SecondList Item 0");
|
||||
list4->add(cursor.block());
|
||||
|
||||
// Ensure list numbers are incremented properly.
|
||||
cursor.insertBlock();
|
||||
cursor.insertText("SecondList Item 1");
|
||||
list4->add(cursor.block());
|
||||
|
||||
const QString output = documentToUnixMarkdown();
|
||||
const QString expected = QString::fromLatin1(
|
||||
R"(2. ListItem 1
|
||||
1) ListItem 2
|
||||
1. ListItem 3
|
||||
3. ListItem 4
|
||||
2) ListItem 5
|
||||
0. SecondList Item 0
|
||||
1. SecondList Item 1
|
||||
)");
|
||||
|
||||
#ifdef DEBUG_WRITE_OUTPUT
|
||||
{
|
||||
QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".md"));
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(output.toUtf8());
|
||||
out.close();
|
||||
}
|
||||
{
|
||||
QFile out(QDir::temp().filePath(QLatin1String(QTest::currentTestFunction()) + ".html"));
|
||||
out.open(QFile::WriteOnly);
|
||||
out.write(document->toHtml().toUtf8());
|
||||
out.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (output != expected && isMainFontFixed())
|
||||
QEXPECT_FAIL("", "fixed-pitch main font (QTBUG-103484)", Continue);
|
||||
QCOMPARE(output, expected);
|
||||
}
|
||||
|
||||
void tst_QTextMarkdownWriter::testWriteTable()
|
||||
{
|
||||
QTextCursor cursor(document);
|
||||
|
Loading…
Reference in New Issue
Block a user