diff --git a/CHANGELOG.md b/CHANGELOG.md
index 346c0d6b..8068d3c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
with the exception that 0.x versions can break between minor versions.
+## Unreleased
+### Fixed
+- footnotes: Fix parsing of footnote definitions containing multiple paragraphs
+ separated by blank lines. Before it only worked if paragraphs were separated
+ by lines of 4 spaces. (#388)
+
## [0.25.0] - 2025-06-20
### Added
- Include OSGi metadata in jars (`META-INF/MANIFEST.MF` files) (#378)
diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java
index 59ee5529..110bdef2 100644
--- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java
+++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java
@@ -39,6 +39,10 @@ public BlockContinue tryContinue(ParserState parserState) {
if (parserState.getIndent() >= 4) {
// It looks like content needs to be indented by 4 so that it's part of a footnote (instead of starting a new block).
return BlockContinue.atColumn(4);
+ } else if (parserState.isBlank()) {
+ // A blank line doesn't finish a footnote yet. If there's another line with indent >= 4 after it,
+ // that should result in another paragraph in this footnote definition.
+ return BlockContinue.atIndex(parserState.getIndex());
} else {
// We're not continuing to give other block parsers a chance to interrupt this definition.
// But if no other block parser applied (including another FootnotesBlockParser), we will
diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java
index eddf6f2e..3dcf4fc8 100644
--- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java
+++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java
@@ -55,9 +55,7 @@ private void renderDefinition(FootnoteDefinition def) {
writer.raw("]: ");
writer.pushPrefix(" ");
- writer.pushTight(true);
renderChildren(def);
- writer.popTight();
writer.popPrefix();
}
diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java
index be3af671..2f1125a0 100644
--- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java
+++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java
@@ -23,7 +23,7 @@ public void testSimple() {
@Test
public void testUnreferenced() {
// Whether a reference has a corresponding definition or vice versa shouldn't matter for Markdown rendering.
- assertRoundTrip("Test [^foo]\n\n[^foo]: one\n[^bar]: two\n");
+ assertRoundTrip("Test [^foo]\n\n[^foo]: one\n\n[^bar]: two\n");
}
@Test
@@ -36,6 +36,18 @@ public void testBackslashInLabel() {
assertRoundTrip("[^\\foo]\n\n[^\\foo]: note\n");
}
+ @Test
+ public void testMultipleLines() {
+ assertRoundTrip("Test [^1]\n\n[^1]: footnote l1\n footnote l2\n");
+ }
+
+ @Test
+ public void testMultipleParagraphs() {
+ // Note that the line between p1 and p2 could be blank too (instead of 4 spaces), but we currently don't
+ // preserve that information.
+ assertRoundTrip("Test [^1]\n\n[^1]: footnote p1\n \n footnote p2\n");
+ }
+
@Test
public void testInline() {
assertRoundTrip("^[test *foo*]\n");
diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
index 3fa726d8..2477dbdf 100644
--- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
+++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
@@ -102,6 +102,26 @@ public void testDefContainsMultipleLines() {
assertText("still", paragraph.getLastChild());
}
+ @Test
+ public void testDefContainsMultipleParagraphs() {
+ var doc = PARSER.parse("[^1]: footnote p1\n\n footnote p2\n");
+ var def = find(doc, FootnoteDefinition.class);
+ assertThat(def.getLabel()).isEqualTo("1");
+ var p1 = (Paragraph) def.getFirstChild();
+ assertText("footnote p1", p1.getFirstChild());
+ var p2 = (Paragraph) p1.getNext();
+ assertText("footnote p2", p2.getFirstChild());
+ }
+
+ @Test
+ public void testDefFollowedByParagraph() {
+ var doc = PARSER.parse("[^1]: footnote\n\nnormal paragraph\n");
+ var def = find(doc, FootnoteDefinition.class);
+ assertThat(def.getLabel()).isEqualTo("1");
+ assertText("footnote", def.getFirstChild().getFirstChild());
+ assertText("normal paragraph", def.getNext().getFirstChild());
+ }
+
@Test
public void testDefContainsList() {
var doc = PARSER.parse("[^1]: - foo\n - bar\n");
diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml
index d5e108b0..b495a85a 100644
--- a/commonmark-integration-test/pom.xml
+++ b/commonmark-integration-test/pom.xml
@@ -20,6 +20,10 @@
org.commonmark
commonmark-ext-autolink
+
+ org.commonmark
+ commonmark-ext-footnotes
+
org.commonmark
commonmark-ext-ins
diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java
index ee7ee529..8df0408c 100644
--- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java
+++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java
@@ -2,6 +2,7 @@
import org.commonmark.Extension;
import org.commonmark.ext.autolink.AutolinkExtension;
+import org.commonmark.ext.footnotes.FootnotesExtension;
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
import org.commonmark.ext.gfm.tables.TablesExtension;
@@ -15,6 +16,7 @@ public class Extensions {
static final List ALL_EXTENSIONS = List.of(
AutolinkExtension.create(),
+ FootnotesExtension.create(),
ImageAttributesExtension.create(),
InsExtension.create(),
StrikethroughExtension.create(),
diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java
index 9ab5c32a..fe14273a 100644
--- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java
+++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java
@@ -18,16 +18,8 @@
public class MarkdownRendererIntegrationTest {
- private static final List EXTENSIONS = List.of(
- AutolinkExtension.create(),
- ImageAttributesExtension.create(),
- InsExtension.create(),
- StrikethroughExtension.create(),
- TablesExtension.create(),
- TaskListItemsExtension.create(),
- YamlFrontMatterExtension.create());
- private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build();
- private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build();
+ private static final Parser PARSER = Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build();
+ private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).build();
@Test
public void testStrikethroughInTable() {
diff --git a/pom.xml b/pom.xml
index 6fd0c8df..d036b145 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,6 +139,11 @@
commonmark-ext-autolink
0.25.1-SNAPSHOT
+
+ org.commonmark
+ commonmark-ext-footnotes
+ 0.25.1-SNAPSHOT
+
org.commonmark
commonmark-ext-image-attributes