22
33import org .commonmark .ext .footnotes .FootnoteDefinition ;
44import org .commonmark .ext .footnotes .FootnoteReference ;
5+ import org .commonmark .ext .footnotes .InlineFootnote ;
56import org .commonmark .node .*;
67import org .commonmark .renderer .NodeRenderer ;
78import org .commonmark .renderer .html .HtmlNodeRendererContext ;
@@ -60,7 +61,7 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer {
6061 /**
6162 * Definitions that were referenced, in order in which they should be rendered.
6263 */
63- private final Map <FootnoteDefinition , ReferencedDefinition > referencedDefinitions = new LinkedHashMap <>();
64+ private final Map <Node , ReferencedDefinition > referencedDefinitions = new LinkedHashMap <>();
6465
6566 /**
6667 * Information about references that should be rendered as footnotes. This doesn't contain all references, just the
@@ -75,7 +76,7 @@ public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) {
7576
7677 @ Override
7778 public Set <Class <? extends Node >> getNodeTypes () {
78- return Set .of (FootnoteReference .class , FootnoteDefinition .class );
79+ return Set .of (FootnoteReference .class , InlineFootnote . class , FootnoteDefinition .class );
7980 }
8081
8182 @ Override
@@ -92,8 +93,16 @@ public void render(Node node) {
9293 // This is called for all references, even ones inside definitions that we render at the end.
9394 var ref = (FootnoteReference ) node ;
9495 // Use containsKey because if value is null, we don't need to try registering again.
95- var info = references .containsKey (ref ) ? references .get (ref ) : registerReference (ref );
96- renderReference (ref , info );
96+ var info = references .containsKey (ref ) ? references .get (ref ) : tryRegisterReference (ref );
97+ if (info != null ) {
98+ renderReference (ref , info );
99+ } else {
100+ // A reference without a corresponding definition is rendered as plain text
101+ html .text ("[^" + ref .getLabel () + "]" );
102+ }
103+ } else if (node instanceof InlineFootnote ) {
104+ var info = registerReference (node , null );
105+ renderReference (node , info );
97106 }
98107 }
99108
@@ -114,7 +123,7 @@ public void afterRoot(Node node) {
114123 html .line ();
115124
116125 // Check whether there are any footnotes inside the definitions that we're about to render. For those, we might
117- // need to render more definitions. So do a breadth-first search to find all relevant definition .
126+ // need to render more definitions. So do a breadth-first search to find all relevant definitions .
118127 var check = new LinkedList <>(referencedDefinitions .keySet ());
119128 while (!check .isEmpty ()) {
120129 var def = check .removeFirst ();
@@ -124,7 +133,7 @@ public void afterRoot(Node node) {
124133 if (!referencedDefinitions .containsKey (d )) {
125134 check .addLast (d );
126135 }
127- references .put (ref , registerReference (ref ));
136+ references .put (ref , registerReference (d , d . getLabel () ));
128137 }
129138 }));
130139 }
@@ -140,52 +149,50 @@ public void afterRoot(Node node) {
140149 html .line ();
141150 }
142151
143- private ReferenceInfo registerReference (FootnoteReference ref ) {
152+ private ReferenceInfo tryRegisterReference (FootnoteReference ref ) {
144153 var def = definitionMap .get (ref .getLabel ());
145154 if (def == null ) {
146155 return null ;
147156 }
157+ return registerReference (def , def .getLabel ());
158+ }
148159
160+ private ReferenceInfo registerReference (Node node , String label ) {
149161 // The first referenced definition gets number 1, second one 2, etc.
150- var referencedDef = referencedDefinitions .computeIfAbsent (def , k -> new ReferencedDefinition (referencedDefinitions .size () + 1 ));
162+ var referencedDef = referencedDefinitions .computeIfAbsent (node , k -> {
163+ var num = referencedDefinitions .size () + 1 ;
164+ var key = definitionKey (label , num );
165+ return new ReferencedDefinition (num , key );
166+ });
151167 var definitionNumber = referencedDef .definitionNumber ;
152168 // The reference number for that particular definition. E.g. if there's two references for the same definition,
153169 // the first one is 1, the second one 2, etc. This is needed to give each reference a unique ID so that each
154170 // reference can get its own backlink from the definition.
155171 var refNumber = referencedDef .references .size () + 1 ;
156- var id = referenceId (def .getLabel (), refNumber );
172+ var definitionKey = referencedDef .definitionKey ;
173+ var id = referenceId (definitionKey , refNumber );
157174 referencedDef .references .add (id );
158175
159- var definitionId = definitionId (def .getLabel ());
160-
161- return new ReferenceInfo (id , definitionId , definitionNumber );
176+ return new ReferenceInfo (id , definitionId (definitionKey ), definitionNumber );
162177 }
163178
164- private void renderReference (FootnoteReference ref , ReferenceInfo referenceInfo ) {
165- if (referenceInfo == null ) {
166- // A reference without a corresponding definition is rendered as plain text
167- html .text ("[^" + ref .getLabel () + "]" );
168- return ;
169- }
170-
171- html .tag ("sup" , context .extendAttributes (ref , "sup" , Map .of ("class" , "footnote-ref" )));
179+ private void renderReference (Node node , ReferenceInfo referenceInfo ) {
180+ html .tag ("sup" , context .extendAttributes (node , "sup" , Map .of ("class" , "footnote-ref" )));
172181
173182 var href = "#" + referenceInfo .definitionId ;
174183 var attrs = new LinkedHashMap <String , String >();
175184 attrs .put ("href" , href );
176185 attrs .put ("id" , referenceInfo .id );
177186 attrs .put ("data-footnote-ref" , null );
178- html .tag ("a" , context .extendAttributes (ref , "a" , attrs ));
187+ html .tag ("a" , context .extendAttributes (node , "a" , attrs ));
179188 html .raw (String .valueOf (referenceInfo .definitionNumber ));
180189 html .tag ("/a" );
181190 html .tag ("/sup" );
182191 }
183192
184- private void renderDefinition (FootnoteDefinition def , ReferencedDefinition referencedDefinition ) {
185- // <ol> etc
186- var id = definitionId (def .getLabel ());
193+ private void renderDefinition (Node def , ReferencedDefinition referencedDefinition ) {
187194 var attrs = new LinkedHashMap <String , String >();
188- attrs .put ("id" , id );
195+ attrs .put ("id" , definitionId ( referencedDefinition . definitionKey ) );
189196 html .tag ("li" , context .extendAttributes (def , "li" , attrs ));
190197 html .line ();
191198
@@ -213,6 +220,13 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer
213220 renderBackrefs (def , referencedDefinition );
214221 html .tag ("/p" );
215222 html .line ();
223+ } else if (def instanceof InlineFootnote ) {
224+ html .tag ("p" , context .extendAttributes (def , "p" , Map .of ()));
225+ renderChildren (def );
226+ html .raw (" " );
227+ renderBackrefs (def , referencedDefinition );
228+ html .tag ("/p" );
229+ html .line ();
216230 } else {
217231 renderChildren (def );
218232 html .line ();
@@ -223,7 +237,7 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer
223237 html .line ();
224238 }
225239
226- private void renderBackrefs (FootnoteDefinition def , ReferencedDefinition referencedDefinition ) {
240+ private void renderBackrefs (Node def , ReferencedDefinition referencedDefinition ) {
227241 var refs = referencedDefinition .references ;
228242 for (int i = 0 ; i < refs .size (); i ++) {
229243 var ref = refs .get (i );
@@ -251,12 +265,22 @@ private void renderBackrefs(FootnoteDefinition def, ReferencedDefinition referen
251265 }
252266 }
253267
254- private String referenceId (String label , int number ) {
255- return "fnref- " + label + (number == 1 ? "" : ("-" + number ));
268+ private String referenceId (String definitionKey , int number ) {
269+ return "fnref" + definitionKey + (number == 1 ? "" : ("-" + number ));
256270 }
257271
258- private String definitionId (String label ) {
259- return "fn-" + label ;
272+ private String definitionKey (String label , int number ) {
273+ // Named definitions use the pattern "fn-{name}" and inline definitions use "fn{number}" so as not to conflict.
274+ // "fn{number}" is also what pandoc uses (for all types), starting with number 1.
275+ if (label != null ) {
276+ return "-" + label ;
277+ } else {
278+ return "" + number ;
279+ }
280+ }
281+
282+ private String definitionId (String definitionKey ) {
283+ return "fn" + definitionKey ;
260284 }
261285
262286 private void renderChildren (Node parent ) {
@@ -306,13 +330,18 @@ private static class ReferencedDefinition {
306330 * The definition number, starting from 1, and in order in which they're referenced.
307331 */
308332 final int definitionNumber ;
333+ /**
334+ * The unique key of the definition. Together with a static prefix it forms the ID used in the HTML.
335+ */
336+ final String definitionKey ;
309337 /**
310338 * The IDs of references for this definition, for backrefs.
311339 */
312340 final List <String > references = new ArrayList <>();
313341
314- ReferencedDefinition (int definitionNumber ) {
342+ ReferencedDefinition (int definitionNumber , String definitionKey ) {
315343 this .definitionNumber = definitionNumber ;
344+ this .definitionKey = definitionKey ;
316345 }
317346 }
318347
0 commit comments