From 6a681f99d08ba81062976931c463126d3745c08b Mon Sep 17 00:00:00 2001
From: digitalMoksha
Date: Tue, 2 May 2023 20:36:34 -0500
Subject: [PATCH 1/6] Use footnote name for reference id
---
src/cm.rs | 2 +-
src/html.rs | 31 ++++++++++++++++---------------
src/nodes.rs | 4 ++--
src/parser/inlines.rs | 2 +-
src/parser/mod.rs | 11 ++++++-----
src/tests/api.rs | 2 +-
src/xml.rs | 2 +-
7 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/src/cm.rs b/src/cm.rs
index 1e6f4ed2..518fe8a8 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -356,7 +356,7 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
NodeValue::TableRow(..) => self.format_table_row(entering),
NodeValue::TableCell => self.format_table_cell(node, entering),
NodeValue::FootnoteDefinition(_) => self.format_footnote_definition(entering),
- NodeValue::FootnoteReference(ref r) => {
+ NodeValue::FootnoteReference(ref r, _) => {
self.format_footnote_reference(r.as_bytes(), entering)
}
};
diff --git a/src/html.rs b/src/html.rs
index ab776210..80173dfe 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -701,13 +701,14 @@ impl<'o> HtmlFormatter<'o> {
self.render_sourcepos(node)?;
self.output.write_all(b">")?;
} else {
- if matches!(
- node.parent().unwrap().data.borrow().value,
- NodeValue::FootnoteDefinition(..)
- ) && node.next_sibling().is_none()
- {
- self.output.write_all(b" ")?;
- self.put_footnote_backref()?;
+ match &node.parent().unwrap().data.borrow().value {
+ NodeValue::FootnoteDefinition(name) => {
+ if node.next_sibling().is_none() {
+ self.output.write_all(b" ")?;
+ self.put_footnote_backref(&name)?;
+ }
+ }
+ _ => {}
}
self.output.write_all(b"
\n")?;
}
@@ -931,7 +932,7 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b"")?;
}
}
- NodeValue::FootnoteDefinition(_) => {
+ NodeValue::FootnoteDefinition(ref name) => {
if entering {
if self.footnote_ix == 0 {
self.output.write_all(b" HtmlFormatter<'o> {
self.footnote_ix += 1;
self.output.write_all(b"", self.footnote_ix)?;
+ writeln!(self.output, " id=\"fn-{}\">", name)?;
} else {
- if self.put_footnote_backref()? {
+ if self.put_footnote_backref(name)? {
self.output.write_all(b"\n")?;
}
self.output.write_all(b"\n")?;
}
}
- NodeValue::FootnoteReference(ref r) => {
+ NodeValue::FootnoteReference(ref r, ix) => {
if entering {
self.output.write_all(b"",
- r, r, r
+ r, r, ix
)?;
}
}
@@ -993,7 +994,7 @@ impl<'o> HtmlFormatter<'o> {
Ok(())
}
- fn put_footnote_backref(&mut self) -> io::Result {
+ fn put_footnote_backref(&mut self, name: &str) -> io::Result {
if self.written_footnote_ix >= self.footnote_ix {
return Ok(false);
}
@@ -1001,8 +1002,8 @@ impl<'o> HtmlFormatter<'o> {
self.written_footnote_ix = self.footnote_ix;
write!(
self.output,
- "",
- self.footnote_ix
+ "",
+ name, self.footnote_ix, self.footnote_ix
)?;
Ok(true)
}
diff --git a/src/nodes.rs b/src/nodes.rs
index 6d4c6ceb..a72fa039 100644
--- a/src/nodes.rs
+++ b/src/nodes.rs
@@ -146,7 +146,7 @@ pub enum NodeValue {
Image(NodeLink),
/// **Inline**. A footnote reference; the `String` is the referent footnote's name.
- FootnoteReference(String),
+ FootnoteReference(String, u32),
#[cfg(feature = "shortcodes")]
/// **Inline**. An Emoji character generated from a shortcode. Enable with feature "shortcodes".
@@ -422,7 +422,7 @@ impl NodeValue {
NodeValue::FrontMatter(_) => "frontmatter",
NodeValue::TaskItem { .. } => "taskitem",
NodeValue::Superscript => "superscript",
- NodeValue::FootnoteReference(_) => "footnote_reference",
+ NodeValue::FootnoteReference(_, _) => "footnote_reference",
#[cfg(feature = "shortcodes")]
NodeValue::ShortCode(_) => "shortcode",
}
diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs
index a08832e7..9e7e9218 100644
--- a/src/parser/inlines.rs
+++ b/src/parser/inlines.rs
@@ -1229,7 +1229,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> {
let text = text.unwrap();
if text.len() > 1 && text.as_bytes()[0] == b'^' {
let inl = self.make_inline(
- NodeValue::FootnoteReference(text[1..].to_string()),
+ NodeValue::FootnoteReference(text[1..].to_string(), 0),
// Overridden immediately below.
self.pos,
self.pos,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index d2a08f65..7f2fadc8 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -600,7 +600,8 @@ pub struct Reference {
struct FootnoteDefinition<'a> {
ix: Option,
node: &'a AstNode<'a>,
-}
+ name: String,
+ }
impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
fn new(
@@ -1761,7 +1762,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
if let Some(ix) = f.ix {
match f.node.data.borrow_mut().value {
NodeValue::FootnoteDefinition(ref mut name) => {
- *name = format!("{}", ix);
+ *name = format!("{}", f.name);
}
_ => unreachable!(),
}
@@ -1780,7 +1781,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
node.detach();
map.insert(
strings::normalize_label(name),
- FootnoteDefinition { ix: None, node },
+ FootnoteDefinition { ix: None, node, name: strings::normalize_label(name) },
);
}
_ => {
@@ -1799,7 +1800,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
let mut ast = node.data.borrow_mut();
let mut replace = None;
match ast.value {
- NodeValue::FootnoteReference(ref mut name) => {
+ NodeValue::FootnoteReference(ref mut name, ref mut index) => {
if let Some(ref mut footnote) = map.get_mut(name) {
let ix = match footnote.ix {
Some(ix) => ix,
@@ -1809,7 +1810,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
*ixp
}
};
- *name = format!("{}", ix);
+ *index = ix;
} else {
replace = Some(name.clone());
}
diff --git a/src/tests/api.rs b/src/tests/api.rs
index b12f8a56..f9d4d338 100644
--- a/src/tests/api.rs
+++ b/src/tests/api.rs
@@ -207,7 +207,7 @@ fn exercise_full_api() {
nodes::NodeValue::ShortCode(ne) => {
let _: &str = ne.shortcode();
}
- nodes::NodeValue::FootnoteReference(name) => {
+ nodes::NodeValue::FootnoteReference(name, _) => {
let _: &String = name;
}
}
diff --git a/src/xml.rs b/src/xml.rs
index 53045cbc..7da0dc3b 100644
--- a/src/xml.rs
+++ b/src/xml.rs
@@ -233,7 +233,7 @@ impl<'o> XmlFormatter<'o> {
self.escape(fd.as_bytes())?;
self.output.write_all(b"\"")?;
}
- NodeValue::FootnoteReference(ref fr) => {
+ NodeValue::FootnoteReference(ref fr, _) => {
self.output.write_all(b" label=\"")?;
self.escape(fr.as_bytes())?;
self.output.write_all(b"\"")?;
From 358de5d86dcfff5f5ff3292d8d285cbed285df1f Mon Sep 17 00:00:00 2001
From: Asherah Connor
Date: Thu, 4 May 2023 12:22:54 +1000
Subject: [PATCH 2/6] nodes: add NodeFootnoteReference
---
src/cm.rs | 4 ++--
src/html.rs | 4 ++--
src/nodes.rs | 16 +++++++++++++---
src/parser/inlines.rs | 7 +++++--
src/parser/mod.rs | 18 +++++++++++-------
src/tests/api.rs | 5 +++--
src/xml.rs | 4 ++--
7 files changed, 38 insertions(+), 20 deletions(-)
diff --git a/src/cm.rs b/src/cm.rs
index 518fe8a8..67abc2a8 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -356,8 +356,8 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
NodeValue::TableRow(..) => self.format_table_row(entering),
NodeValue::TableCell => self.format_table_cell(node, entering),
NodeValue::FootnoteDefinition(_) => self.format_footnote_definition(entering),
- NodeValue::FootnoteReference(ref r, _) => {
- self.format_footnote_reference(r.as_bytes(), entering)
+ NodeValue::FootnoteReference(ref nfr) => {
+ self.format_footnote_reference(nfr.name.as_bytes(), entering)
}
};
true
diff --git a/src/html.rs b/src/html.rs
index 80173dfe..a40fd18f 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -951,13 +951,13 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b"\n")?;
}
}
- NodeValue::FootnoteReference(ref r, ix) => {
+ NodeValue::FootnoteReference(ref nfr) => {
if entering {
self.output.write_all(b"",
- r, r, ix
+ nfr.name, nfr.name, nfr.ix,
)?;
}
}
diff --git a/src/nodes.rs b/src/nodes.rs
index a72fa039..959eaf1c 100644
--- a/src/nodes.rs
+++ b/src/nodes.rs
@@ -145,8 +145,8 @@ pub enum NodeValue {
/// **Inline**. An [image](https://github.github.com/gfm/#images).
Image(NodeLink),
- /// **Inline**. A footnote reference; the `String` is the referent footnote's name.
- FootnoteReference(String, u32),
+ /// **Inline**. A footnote reference.
+ FootnoteReference(NodeFootnoteReference),
#[cfg(feature = "shortcodes")]
/// **Inline**. An Emoji character generated from a shortcode. Enable with feature "shortcodes".
@@ -329,6 +329,16 @@ pub struct NodeHtmlBlock {
pub literal: String,
}
+/// The metadata of a footnote reference.
+#[derive(Debug, Default, Clone)]
+pub struct NodeFootnoteReference {
+ /// The name of the footnote.
+ pub name: String,
+
+ /// The index of the footnote in the document.
+ pub ix: u32,
+}
+
impl NodeValue {
/// Indicates whether this node is a block node or inline node.
pub fn block(&self) -> bool {
@@ -422,7 +432,7 @@ impl NodeValue {
NodeValue::FrontMatter(_) => "frontmatter",
NodeValue::TaskItem { .. } => "taskitem",
NodeValue::Superscript => "superscript",
- NodeValue::FootnoteReference(_, _) => "footnote_reference",
+ NodeValue::FootnoteReference(..) => "footnote_reference",
#[cfg(feature = "shortcodes")]
NodeValue::ShortCode(_) => "shortcode",
}
diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs
index 9e7e9218..0ea66beb 100644
--- a/src/parser/inlines.rs
+++ b/src/parser/inlines.rs
@@ -1,7 +1,7 @@
use crate::arena_tree::Node;
use crate::ctype::{ispunct, isspace};
use crate::entity;
-use crate::nodes::{Ast, AstNode, NodeCode, NodeLink, NodeValue, Sourcepos};
+use crate::nodes::{Ast, AstNode, NodeCode, NodeFootnoteReference, NodeLink, NodeValue, Sourcepos};
#[cfg(feature = "shortcodes")]
use crate::parser::shortcodes::NodeShortCode;
use crate::parser::{
@@ -1229,7 +1229,10 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> {
let text = text.unwrap();
if text.len() > 1 && text.as_bytes()[0] == b'^' {
let inl = self.make_inline(
- NodeValue::FootnoteReference(text[1..].to_string(), 0),
+ NodeValue::FootnoteReference(NodeFootnoteReference {
+ name: text[1..].to_string(),
+ ix: 0,
+ }),
// Overridden immediately below.
self.pos,
self.pos,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 7f2fadc8..2bd54786 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -601,7 +601,7 @@ struct FootnoteDefinition<'a> {
ix: Option,
node: &'a AstNode<'a>,
name: String,
- }
+}
impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
fn new(
@@ -1759,7 +1759,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
let mut v = map.into_values().collect::>();
v.sort_unstable_by(|a, b| a.ix.cmp(&b.ix));
for f in v {
- if let Some(ix) = f.ix {
+ if f.ix.is_some() {
match f.node.data.borrow_mut().value {
NodeValue::FootnoteDefinition(ref mut name) => {
*name = format!("{}", f.name);
@@ -1781,7 +1781,11 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
node.detach();
map.insert(
strings::normalize_label(name),
- FootnoteDefinition { ix: None, node, name: strings::normalize_label(name) },
+ FootnoteDefinition {
+ ix: None,
+ node,
+ name: strings::normalize_label(name),
+ },
);
}
_ => {
@@ -1800,8 +1804,8 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
let mut ast = node.data.borrow_mut();
let mut replace = None;
match ast.value {
- NodeValue::FootnoteReference(ref mut name, ref mut index) => {
- if let Some(ref mut footnote) = map.get_mut(name) {
+ NodeValue::FootnoteReference(ref mut nfr) => {
+ if let Some(ref mut footnote) = map.get_mut(&nfr.name) {
let ix = match footnote.ix {
Some(ix) => ix,
None => {
@@ -1810,9 +1814,9 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
*ixp
}
};
- *index = ix;
+ nfr.ix = ix;
} else {
- replace = Some(name.clone());
+ replace = Some(nfr.name.clone());
}
}
_ => {
diff --git a/src/tests/api.rs b/src/tests/api.rs
index f9d4d338..af3f01c5 100644
--- a/src/tests/api.rs
+++ b/src/tests/api.rs
@@ -207,8 +207,9 @@ fn exercise_full_api() {
nodes::NodeValue::ShortCode(ne) => {
let _: &str = ne.shortcode();
}
- nodes::NodeValue::FootnoteReference(name, _) => {
- let _: &String = name;
+ nodes::NodeValue::FootnoteReference(nfr) => {
+ let _: String = nfr.name;
+ let _: u32 = nfr.ix;
}
}
}
diff --git a/src/xml.rs b/src/xml.rs
index 7da0dc3b..092a9457 100644
--- a/src/xml.rs
+++ b/src/xml.rs
@@ -233,9 +233,9 @@ impl<'o> XmlFormatter<'o> {
self.escape(fd.as_bytes())?;
self.output.write_all(b"\"")?;
}
- NodeValue::FootnoteReference(ref fr, _) => {
+ NodeValue::FootnoteReference(ref nfr) => {
self.output.write_all(b" label=\"")?;
- self.escape(fr.as_bytes())?;
+ self.escape(nfr.name.as_bytes())?;
self.output.write_all(b"\"")?;
}
NodeValue::TaskItem(Some(_)) => {
From 3501ab0c19cadac78f425f7a0e59463a13bacf94 Mon Sep 17 00:00:00 2001
From: digitalMoksha
Date: Thu, 11 May 2023 16:03:37 -0500
Subject: [PATCH 3/6] Fix footnote tests
---
src/cm.rs | 7 +++----
src/parser/mod.rs | 2 +-
src/tests/footnotes.rs | 39 +++++++++++++++++++--------------------
3 files changed, 23 insertions(+), 25 deletions(-)
diff --git a/src/cm.rs b/src/cm.rs
index 67abc2a8..1d5f2c97 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -355,7 +355,7 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
NodeValue::Table(..) => self.format_table(entering),
NodeValue::TableRow(..) => self.format_table_row(entering),
NodeValue::TableCell => self.format_table_cell(node, entering),
- NodeValue::FootnoteDefinition(_) => self.format_footnote_definition(entering),
+ NodeValue::FootnoteDefinition(ref name) => self.format_footnote_definition(name, entering),
NodeValue::FootnoteReference(ref nfr) => {
self.format_footnote_reference(nfr.name.as_bytes(), entering)
}
@@ -738,11 +738,10 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
}
}
}
- fn format_footnote_definition(&mut self, entering: bool) {
+ fn format_footnote_definition(&mut self, name: &String, entering: bool) {
if entering {
self.footnote_ix += 1;
- let footnote_ix = self.footnote_ix;
- writeln!(self, "[^{}]:", footnote_ix).unwrap();
+ writeln!(self, "[^{}]:", name).unwrap();
write!(self.prefix, " ").unwrap();
} else {
let new_len = self.prefix.len() - 4;
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 2bd54786..2f0a270b 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -256,7 +256,7 @@ pub struct ComrakExtensionOptions {
/// let mut options = ComrakOptions::default();
/// options.extension.footnotes = true;
/// assert_eq!(markdown_to_html("Hi[^x].\n\n[^x]: A greeting.\n", &options),
- /// "Hi.
\n\n");
+ /// "Hi.
\n\n");
/// ```
pub footnotes: bool,
diff --git a/src/tests/footnotes.rs b/src/tests/footnotes.rs
index 0420363e..f23c52a0 100644
--- a/src/tests/footnotes.rs
+++ b/src/tests/footnotes.rs
@@ -26,26 +26,26 @@ fn footnotes() {
concat!(
"Here is a[^nowhere] footnote reference, and another.
\n",
- "This is another note.
\n",
+ href=\"#fn-longnote\" id=\"fnref-longnote\" data-footnote-ref>2\n",
+ "This is another note.
\n",
"This is regular content.
\n",
"\n"
@@ -59,12 +59,11 @@ fn footnote_does_not_eat_exclamation() {
[extension.footnotes],
concat!("Here's my footnote![^a]\n", "\n", "[^a]: Yep.\n"),
concat!(
- "Here's my footnote!
\n",
+ "Here's my footnote!
\n",
"\n"
@@ -103,7 +102,7 @@ fn footnote_in_table() {
"\n",
@@ -127,18 +126,18 @@ fn footnote_with_superscript() {
concat!(
"Here is a footnote reference.
\n",
- "Here is a longer footnote reference.
\n",
+ "Here is a longer footnote reference.
\n",
"e = mc2.
\n",
"\n"
From 1ab6c30031755f17dfff0e278074db3fda7237d9 Mon Sep 17 00:00:00 2001
From: digitalMoksha
Date: Fri, 12 May 2023 19:37:08 -0500
Subject: [PATCH 4/6] Output information when a footnote is referenced multiple
times
---
src/cm.rs | 4 +++-
src/html.rs | 46 +++++++++++++++++++++++++++++-------------
src/nodes.rs | 15 +++++++++++++-
src/parser/inlines.rs | 1 +
src/parser/mod.rs | 22 +++++++++++++-------
src/tests/api.rs | 5 +++--
src/tests/footnotes.rs | 14 +++++++------
src/xml.rs | 2 +-
8 files changed, 77 insertions(+), 32 deletions(-)
diff --git a/src/cm.rs b/src/cm.rs
index 1d5f2c97..1e448fbc 100644
--- a/src/cm.rs
+++ b/src/cm.rs
@@ -355,7 +355,9 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
NodeValue::Table(..) => self.format_table(entering),
NodeValue::TableRow(..) => self.format_table_row(entering),
NodeValue::TableCell => self.format_table_cell(node, entering),
- NodeValue::FootnoteDefinition(ref name) => self.format_footnote_definition(name, entering),
+ NodeValue::FootnoteDefinition(ref nfd) => {
+ self.format_footnote_definition(&nfd.name, entering)
+ },
NodeValue::FootnoteReference(ref nfr) => {
self.format_footnote_reference(nfr.name.as_bytes(), entering)
}
diff --git a/src/html.rs b/src/html.rs
index a40fd18f..2a86f70a 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -1,6 +1,6 @@
//! The HTML renderer for the CommonMark AST, as well as helper functions.
use crate::ctype::isspace;
-use crate::nodes::{AstNode, ListType, NodeCode, NodeValue, TableAlignment};
+use crate::nodes::{AstNode, ListType, NodeCode, NodeFootnoteDefinition, NodeValue, TableAlignment};
use crate::parser::{ComrakOptions, ComrakPlugins};
use crate::scanners;
use once_cell::sync::Lazy;
@@ -702,10 +702,10 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b">")?;
} else {
match &node.parent().unwrap().data.borrow().value {
- NodeValue::FootnoteDefinition(name) => {
+ NodeValue::FootnoteDefinition(nfd) => {
if node.next_sibling().is_none() {
self.output.write_all(b" ")?;
- self.put_footnote_backref(&name)?;
+ self.put_footnote_backref(&nfd)?;
}
}
_ => {}
@@ -932,7 +932,7 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b"")?;
}
}
- NodeValue::FootnoteDefinition(ref name) => {
+ NodeValue::FootnoteDefinition(ref nfd) => {
if entering {
if self.footnote_ix == 0 {
self.output.write_all(b" HtmlFormatter<'o> {
self.footnote_ix += 1;
self.output.write_all(b"", name)?;
+ writeln!(self.output, " id=\"fn-{}\">", nfd.name)?;
} else {
- if self.put_footnote_backref(name)? {
+ if self.put_footnote_backref(&nfd)? {
self.output.write_all(b"\n")?;
}
self.output.write_all(b"\n")?;
@@ -953,11 +953,17 @@ impl<'o> HtmlFormatter<'o> {
}
NodeValue::FootnoteReference(ref nfr) => {
if entering {
+ let mut ref_id = format!("fnref-{}", nfr.name);
+
self.output.write_all(b" 1 {
+ ref_id = format!("{}-{}", ref_id, nfr.ref_num);
+ }
write!(
- self.output, " class=\"footnote-ref\">{}",
- nfr.name, nfr.name, nfr.ix,
+ self.output, " class=\"footnote-ref\">{}",
+ nfr.name, ref_id, nfr.ix,
)?;
}
}
@@ -994,17 +1000,29 @@ impl<'o> HtmlFormatter<'o> {
Ok(())
}
- fn put_footnote_backref(&mut self, name: &str) -> io::Result {
+ fn put_footnote_backref(&mut self, nfd: &NodeFootnoteDefinition) -> io::Result {
if self.written_footnote_ix >= self.footnote_ix {
return Ok(false);
}
self.written_footnote_ix = self.footnote_ix;
- write!(
- self.output,
- "",
- name, self.footnote_ix, self.footnote_ix
- )?;
+
+ let mut ref_suffix = String::new();
+ let mut superscript = String::new();
+
+ for ref_num in 1..=nfd.total_references {
+ if ref_num > 1 {
+ ref_suffix = format!("-{}", ref_num);
+ superscript = format!("", ref_num);
+ write!(self.output, " ")?;
+ }
+
+ write!(
+ self.output,
+ "",
+ nfd.name, ref_suffix, self.footnote_ix, ref_suffix, self.footnote_ix, ref_suffix, superscript
+ )?;
+ }
Ok(true)
}
}
diff --git a/src/nodes.rs b/src/nodes.rs
index 959eaf1c..6856ee1f 100644
--- a/src/nodes.rs
+++ b/src/nodes.rs
@@ -88,7 +88,7 @@ pub enum NodeValue {
/// **Block**. A footnote definition. The `String` is the footnote's name.
/// Contains other **blocks**.
- FootnoteDefinition(String),
+ FootnoteDefinition(NodeFootnoteDefinition),
/// **Block**. A [table](https://github.github.com/gfm/#tables-extension-) per the GFM spec.
/// Contains table rows.
@@ -329,12 +329,25 @@ pub struct NodeHtmlBlock {
pub literal: String,
}
+/// The metadata of a footnote definition.
+#[derive(Debug, Default, Clone)]
+pub struct NodeFootnoteDefinition {
+ /// The name of the footnote.
+ pub name: String,
+
+ /// Total number of references to this footnote
+ pub total_references: u32,
+}
+
/// The metadata of a footnote reference.
#[derive(Debug, Default, Clone)]
pub struct NodeFootnoteReference {
/// The name of the footnote.
pub name: String,
+ /// The index of reference to the same footnote
+ pub ref_num: u32,
+
/// The index of the footnote in the document.
pub ix: u32,
}
diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs
index 0ea66beb..f56f82c6 100644
--- a/src/parser/inlines.rs
+++ b/src/parser/inlines.rs
@@ -1231,6 +1231,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> {
let inl = self.make_inline(
NodeValue::FootnoteReference(NodeFootnoteReference {
name: text[1..].to_string(),
+ ref_num: 0,
ix: 0,
}),
// Overridden immediately below.
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 2f0a270b..96701bb7 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8,7 +8,7 @@ use crate::adapters::SyntaxHighlighterAdapter;
use crate::arena_tree::Node;
use crate::ctype::{isdigit, isspace};
use crate::entity;
-use crate::nodes::{self, Sourcepos};
+use crate::nodes::{self, NodeFootnoteDefinition, Sourcepos};
use crate::nodes::{
Ast, AstNode, ListDelimType, ListType, NodeCodeBlock, NodeDescriptionItem, NodeHeading,
NodeHtmlBlock, NodeList, NodeValue,
@@ -601,6 +601,7 @@ struct FootnoteDefinition<'a> {
ix: Option,
node: &'a AstNode<'a>,
name: String,
+ total_references: u32,
}
impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
@@ -1081,7 +1082,10 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
self.advance_offset(line, offset, false);
*container = self.add_child(
container,
- NodeValue::FootnoteDefinition(str::from_utf8(c).unwrap().to_string()),
+ NodeValue::FootnoteDefinition(NodeFootnoteDefinition {
+ name: str::from_utf8(c).unwrap().to_string(),
+ total_references: 0,
+ }),
self.first_nonspace + 1,
);
container.data.borrow_mut().internal_offset = matched;
@@ -1761,8 +1765,9 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
for f in v {
if f.ix.is_some() {
match f.node.data.borrow_mut().value {
- NodeValue::FootnoteDefinition(ref mut name) => {
- *name = format!("{}", f.name);
+ NodeValue::FootnoteDefinition(ref mut nfd) => {
+ nfd.name = format!("{}", f.name);
+ nfd.total_references = f.total_references;
}
_ => unreachable!(),
}
@@ -1777,14 +1782,15 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
map: &mut HashMap>,
) {
match node.data.borrow().value {
- NodeValue::FootnoteDefinition(ref name) => {
+ NodeValue::FootnoteDefinition(ref nfd) => {
node.detach();
map.insert(
- strings::normalize_label(name),
+ strings::normalize_label(&nfd.name),
FootnoteDefinition {
ix: None,
node,
- name: strings::normalize_label(name),
+ name: strings::normalize_label(&nfd.name),
+ total_references: 0,
},
);
}
@@ -1814,6 +1820,8 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
*ixp
}
};
+ footnote.total_references += 1;
+ nfr.ref_num = footnote.total_references;
nfr.ix = ix;
} else {
replace = Some(nfr.name.clone());
diff --git a/src/tests/api.rs b/src/tests/api.rs
index af3f01c5..2267db42 100644
--- a/src/tests/api.rs
+++ b/src/tests/api.rs
@@ -164,8 +164,9 @@ fn exercise_full_api() {
let _: bool = nh.setext;
}
nodes::NodeValue::ThematicBreak => {}
- nodes::NodeValue::FootnoteDefinition(name) => {
- let _: &String = name;
+ nodes::NodeValue::FootnoteDefinition(nfd) => {
+ let _: &String = &nfd.name;
+ let _: u32 = nfd.total_references;
}
nodes::NodeValue::Table(aligns) => {
let _: &Vec = aligns;
diff --git a/src/tests/footnotes.rs b/src/tests/footnotes.rs
index f23c52a0..c16fa530 100644
--- a/src/tests/footnotes.rs
+++ b/src/tests/footnotes.rs
@@ -7,7 +7,7 @@ fn footnotes() {
concat!(
"Here is a[^nowhere] footnote reference,[^1] and another.[^longnote]\n",
"\n",
- "This is another note.[^note]\n",
+ "This is another note.[^note] And footnote[^longnote] is referenced again.\n",
"\n",
"[^note]: Hi.\n",
"\n",
@@ -27,8 +27,9 @@ fn footnotes() {
"Here is a[^nowhere] footnote reference, and another.
\n",
- "This is another note.
\n",
+ "This is another note. And footnote is referenced again.
\n",
"This is regular content.
\n",
"