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