diff --git a/znai-core/src/main/java/org/testingisdocumenting/znai/extensions/footnote/FootnoteId.java b/znai-core/src/main/java/org/testingisdocumenting/znai/extensions/footnote/FootnoteId.java index 50af5f903..ad86f0247 100644 --- a/znai-core/src/main/java/org/testingisdocumenting/znai/extensions/footnote/FootnoteId.java +++ b/znai-core/src/main/java/org/testingisdocumenting/znai/extensions/footnote/FootnoteId.java @@ -17,11 +17,4 @@ package org.testingisdocumenting.znai.extensions.footnote; public record FootnoteId(String id) { - public int asNumber() { - return Integer.parseInt(id); - } - - public boolean isNumber() { - return id != null && id.matches("\\d+"); - } } diff --git a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownParser.java b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownParser.java index 5b9481165..8069ecdc5 100644 --- a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownParser.java +++ b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownParser.java @@ -68,6 +68,11 @@ public MarkupParserResult parse(Path path, String markdown) { Node node = fullParser.parse(markdown); MarkdownVisitor visitor = parsePartial(node, path, parserHandler); + if (!visitor.getUnresolvedFootnoteRefs().isEmpty()) { + throw new IllegalArgumentException("undefined footnote reference(s): " + + String.join(", ", visitor.getUnresolvedFootnoteRefs())); + } + if (visitor.hasPluginWarnings()) { reportWarnings(path, visitor.getParameterWarnings()); } diff --git a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownVisitor.java b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownVisitor.java index 910e8a2b0..ea33762b8 100644 --- a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownVisitor.java +++ b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/commonmark/MarkdownVisitor.java @@ -44,20 +44,26 @@ import java.nio.file.Path; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class MarkdownVisitor extends AbstractVisitor { + private static final Pattern FOOTNOTE_REFERENCE_PATTERN = Pattern.compile("\\[\\^([^]]+)]"); + private final ComponentsRegistry componentsRegistry; private final Path path; private final ParserHandler parserHandler; private boolean sectionStarted; private final Set parameterWarnings; + private final Set unresolvedFootnoteRefs; public MarkdownVisitor(ComponentsRegistry componentsRegistry, Path path, ParserHandler parserHandler) { this.componentsRegistry = componentsRegistry; this.path = path; this.parserHandler = parserHandler; this.parameterWarnings = new LinkedHashSet<>(); + this.unresolvedFootnoteRefs = new LinkedHashSet<>(); } public boolean isSectionStarted() { @@ -72,6 +78,10 @@ public Set getParameterWarnings() { return parameterWarnings; } + public Set getUnresolvedFootnoteRefs() { + return unresolvedFootnoteRefs; + } + @Override public void visit(Paragraph paragraph) { parserHandler.onParagraphStart(); @@ -95,7 +105,13 @@ public void visit(StrongEmphasis strongEmphasis) { @Override public void visit(Text text) { - parserHandler.onSimpleText(text.getLiteral()); + String literal = text.getLiteral(); + Matcher matcher = FOOTNOTE_REFERENCE_PATTERN.matcher(literal); + while (matcher.find()) { + unresolvedFootnoteRefs.add(matcher.group(1)); + } + + parserHandler.onSimpleText(literal); } @Override diff --git a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/docelement/DocElementCreationParserHandler.java b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/docelement/DocElementCreationParserHandler.java index 2184c5bf7..3bf71b759 100644 --- a/znai-core/src/main/java/org/testingisdocumenting/znai/parser/docelement/DocElementCreationParserHandler.java +++ b/znai-core/src/main/java/org/testingisdocumenting/znai/parser/docelement/DocElementCreationParserHandler.java @@ -225,10 +225,7 @@ public void onTable(MarkupTableData tableData) { public void onFootnoteDefinition(ParsedFootnote footnote) { parsedFootnotes.put(footnote.id(), footnote); - int indexToUse = footnote.id().isNumber() ? - footnote.id().asNumber(): - ++footnoteAutoIdx; - + int indexToUse = ++footnoteAutoIdx; footnoteIdxById.put(footnote.id(), indexToUse); } diff --git a/znai-core/src/test/groovy/org/testingisdocumenting/znai/parser/MarkdownParserTest.groovy b/znai-core/src/test/groovy/org/testingisdocumenting/znai/parser/MarkdownParserTest.groovy index 68a626c31..b3d324ae4 100644 --- a/znai-core/src/test/groovy/org/testingisdocumenting/znai/parser/MarkdownParserTest.groovy +++ b/znai-core/src/test/groovy/org/testingisdocumenting/znai/parser/MarkdownParserTest.groovy @@ -617,7 +617,7 @@ after footnote ["type": "SoftLineBreak"], ["text": "numeric one ", "type": "SimpleText"], [ - "label": "5", + "label": "3", "content": [["type": "Paragraph", "content": [["text": "footnote num5", "type": "SimpleText"]]]], "type": "FootnoteReference" ], @@ -625,7 +625,7 @@ after footnote ["type": "SoftLineBreak"], ["text": "and what do we do with this ", "type": "SimpleText"], [ - "label": "8", + "label": "4", "content": [["type": "Paragraph", "content": [["text": "footnote num8", "type": "SimpleText"]]]], "type": "FootnoteReference" ] @@ -637,6 +637,16 @@ after footnote ] } + @Test + void "undefined footnote reference"() { + code { + parse("text with [^undefined] reference") + } should throwException(~/undefined footnote reference\(s\): undefined/) + + parse("text with `[^undefined]` reference") + parse("```[^ref]```") + } + @Test void "embedded html block"() { parse(""" diff --git a/znai-docs/znai/flow/footnotes.md b/znai-docs/znai/flow/footnotes.md index 8b48cb70b..b9c2867c5 100644 --- a/znai-docs/znai/flow/footnotes.md +++ b/znai-docs/znai/flow/footnotes.md @@ -18,6 +18,9 @@ To add a reference to the footnote use `[^my-id]` which will result in [^my-id] Constructor() ``` +Note: numeric footnotes are treated as text footnotes, and they will be assigned the auto incremented +number in order of appearance. + # Preview Hover mouse over a footnote reference see its content in a tooltip. diff --git a/znai-docs/znai/release-notes/1.88/add-2026-04-09-footnotes-numeric-auto-increment.md b/znai-docs/znai/release-notes/1.88/add-2026-04-09-footnotes-numeric-auto-increment.md new file mode 100644 index 000000000..95688808f --- /dev/null +++ b/znai-docs/znai/release-notes/1.88/add-2026-04-09-footnotes-numeric-auto-increment.md @@ -0,0 +1 @@ +* Add: numeric Footnotes auto increment and ignore the given numeric value diff --git a/znai-docs/znai/release-notes/1.88/add-2026-04-11-undefined-footnote-ref.md b/znai-docs/znai/release-notes/1.88/add-2026-04-11-undefined-footnote-ref.md new file mode 100644 index 000000000..727841f68 --- /dev/null +++ b/znai-docs/znai/release-notes/1.88/add-2026-04-11-undefined-footnote-ref.md @@ -0,0 +1 @@ +* Add: Undefined footnote references fail the build \ No newline at end of file