summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-02-15 01:34:54 +0100
committerShawn Rutledge <shawn.rutledge@qt.io>2023-03-22 10:50:58 +0100
commit3f40a8b5b1f7d83130d6ec2b85a28a782072b6a9 (patch)
tree6968953bf1b6388f8bdcfb0a63dc26b7ee8a7dea
parentb05540bd00d051c74e67d138f5148eb23e018996 (diff)
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>
-rw-r--r--src/gui/text/qtextdocument.cpp17
-rw-r--r--src/gui/text/qtextdocumentfragment.cpp2
-rw-r--r--src/gui/text/qtextformat.cpp30
-rw-r--r--src/gui/text/qtextformat.h9
-rw-r--r--src/gui/text/qtexthtmlparser.cpp2
-rw-r--r--src/gui/text/qtexthtmlparser_p.h1
-rw-r--r--src/gui/text/qtextlist.cpp23
-rw-r--r--src/gui/text/qtextmarkdownimporter.cpp3
-rw-r--r--src/gui/text/qtextmarkdownwriter.cpp4
-rw-r--r--tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp11
-rw-r--r--tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp16
-rw-r--r--tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp17
-rw-r--r--tests/auto/gui/text/qtextlist/tst_qtextlist.cpp58
-rw-r--r--tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp55
-rw-r--r--tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp107
15 files changed, 325 insertions, 30 deletions
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
index 3572988e3e..e5dc5136cb 100644
--- a/src/gui/text/qtextdocument.cpp
+++ b/src/gui/text/qtextdocument.cpp
@@ -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)) {
diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp
index 7d8b2eaa93..9e99992929 100644
--- a/src/gui/text/qtextdocumentfragment.cpp
+++ b/src/gui/text/qtextdocumentfragment.cpp
@@ -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)
diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp
index 508d472062..8be3a0d442 100644
--- a/src/gui/text/qtextformat.cpp
+++ b/src/gui/text/qtextformat.cpp
@@ -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);
}
/*!
@@ -2751,6 +2753,32 @@ QTextListFormat::QTextListFormat(const QTextFormat &fmt)
*/
/*!
+ \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
diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h
index 02c66279e2..c009d328cb 100644
--- a/src/gui/text/qtextformat.h
+++ b/src/gui/text/qtextformat.h
@@ -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:
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
index 55c64bb256..16e2d7bd3a 100644
--- a/src/gui/text/qtexthtmlparser.cpp
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -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:
diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h
index 0085c01e87..ecbc238171 100644
--- a/src/gui/text/qtexthtmlparser_p.h
+++ b/src/gui/text/qtexthtmlparser_p.h
@@ -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;
diff --git a/src/gui/text/qtextlist.cpp b/src/gui/text/qtextlist.cpp
index 8b4b308d0c..90a6cf8867 100644
--- a/src/gui/text/qtextlist.cpp
+++ b/src/gui/text/qtextlist.cpp
@@ -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;
}
diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp
index 8838568fd0..bb95678ef1 100644
--- a/src/gui/text/qtextmarkdownimporter.cpp
+++ b/src/gui/text/qtextmarkdownimporter.cpp
@@ -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);
diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp
index f535ff8aa8..5fb67ccc67 100644
--- a/src/gui/text/qtextmarkdownwriter.cpp
+++ b/src/gui/text/qtextmarkdownwriter.cpp
@@ -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()) {
diff --git a/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp b/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp
index 639e2e277f..1780b321ab 100644
--- a/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp
+++ b/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp
@@ -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");
diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
index dc93c86829..f6527fbff6 100644
--- a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
+++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
@@ -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()
diff --git a/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp
index 4cc4a253ee..ca23c56b3e 100644
--- a/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp
+++ b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp
@@ -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>";
diff --git a/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp b/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp
index 134249ee83..b2d880f6cf 100644
--- a/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp
+++ b/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp
@@ -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"
diff --git a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
index 686187cc77..a9b519f2d7 100644
--- a/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
+++ b/tests/auto/gui/text/qtextmarkdownimporter/tst_qtextmarkdownimporter.cpp
@@ -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;
}
diff --git a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
index 2b6b1ecca5..36b494e550 100644
--- a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
+++ b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp
@@ -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);