Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/cm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,11 @@ 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::FootnoteReference(ref r) => {
self.format_footnote_reference(r.as_bytes(), 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)
}
};
true
Expand Down Expand Up @@ -738,11 +740,10 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> {
}
}
}
fn format_footnote_definition(&mut self, entering: bool) {
fn format_footnote_definition(&mut self, name: &str, 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;
Expand Down
58 changes: 39 additions & 19 deletions src/html.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! 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;
Expand Down Expand Up @@ -701,13 +703,13 @@ 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()
if let NodeValue::FootnoteDefinition(nfd) =
&node.parent().unwrap().data.borrow().value
{
self.output.write_all(b" ")?;
self.put_footnote_backref()?;
if node.next_sibling().is_none() {
self.output.write_all(b" ")?;
self.put_footnote_backref(nfd)?;
}
}
self.output.write_all(b"</p>\n")?;
}
Expand Down Expand Up @@ -931,7 +933,7 @@ impl<'o> HtmlFormatter<'o> {
self.output.write_all(b"</td>")?;
}
}
NodeValue::FootnoteDefinition(_) => {
NodeValue::FootnoteDefinition(ref nfd) => {
if entering {
if self.footnote_ix == 0 {
self.output.write_all(b"<section")?;
Expand All @@ -942,21 +944,27 @@ impl<'o> HtmlFormatter<'o> {
self.footnote_ix += 1;
self.output.write_all(b"<li")?;
self.render_sourcepos(node)?;
writeln!(self.output, " id=\"fn-{}\">", self.footnote_ix)?;
writeln!(self.output, " id=\"fn-{}\">", nfd.name)?;
} else {
if self.put_footnote_backref()? {
if self.put_footnote_backref(nfd)? {
self.output.write_all(b"\n")?;
}
self.output.write_all(b"</li>\n")?;
}
}
NodeValue::FootnoteReference(ref r) => {
NodeValue::FootnoteReference(ref nfr) => {
if entering {
let mut ref_id = format!("fnref-{}", nfr.name);

self.output.write_all(b"<sup")?;
self.render_sourcepos(node)?;

if nfr.ref_num > 1 {
ref_id = format!("{}-{}", ref_id, nfr.ref_num);
}
write!(
self.output, " class=\"footnote-ref\"><a href=\"#fn-{}\" id=\"fnref-{}\" data-footnote-ref>{}</a></sup>",
r, r, r
self.output, " class=\"footnote-ref\"><a href=\"#fn-{}\" id=\"{}\" data-footnote-ref>{}</a></sup>",
nfr.name, ref_id, nfr.ix,
)?;
}
}
Expand Down Expand Up @@ -993,17 +1001,29 @@ impl<'o> HtmlFormatter<'o> {
Ok(())
}

fn put_footnote_backref(&mut self) -> io::Result<bool> {
fn put_footnote_backref(&mut self, nfd: &NodeFootnoteDefinition) -> io::Result<bool> {
if self.written_footnote_ix >= self.footnote_ix {
return Ok(false);
}

self.written_footnote_ix = self.footnote_ix;
write!(
self.output,
"<a href=\"#fnref-{}\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩</a>",
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!("<sup class=\"footnote-ref\">{}</sup>", ref_num);
write!(self.output, " ")?;
}

write!(
self.output,
"<a href=\"#fnref-{}{}\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"{}{}\" aria-label=\"Back to reference {}{}\">↩{}</a>",
nfd.name, ref_suffix, self.footnote_ix, ref_suffix, self.footnote_ix, ref_suffix, superscript
)?;
}
Ok(true)
}
}
31 changes: 27 additions & 4 deletions src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
/// **Inline**. A footnote reference.
FootnoteReference(NodeFootnoteReference),

#[cfg(feature = "shortcodes")]
/// **Inline**. An Emoji character generated from a shortcode. Enable with feature "shortcodes".
Expand Down Expand Up @@ -329,6 +329,29 @@ 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,
}

impl NodeValue {
/// Indicates whether this node is a block node or inline node.
pub fn block(&self) -> bool {
Expand Down Expand Up @@ -422,7 +445,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",
}
Expand Down
8 changes: 6 additions & 2 deletions src/parser/inlines.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -1229,7 +1229,11 @@ 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(NodeFootnoteReference {
name: text[1..].to_string(),
ref_num: 0,
ix: 0,
}),
// Overridden immediately below.
self.pos,
self.pos,
Expand Down
39 changes: 26 additions & 13 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
/// "<p>Hi<sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>A greeting. <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩</a></p>\n</li>\n</ol>\n</section>\n");
/// "<p>Hi<sup class=\"footnote-ref\"><a href=\"#fn-x\" id=\"fnref-x\" data-footnote-ref>1</a></sup>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-x\">\n<p>A greeting. <a href=\"#fnref-x\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n");
/// ```
pub footnotes: bool,

Expand Down Expand Up @@ -600,6 +600,8 @@ pub struct Reference {
struct FootnoteDefinition<'a> {
ix: Option<u32>,
node: &'a AstNode<'a>,
name: String,
total_references: u32,
}

impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
Expand Down Expand Up @@ -1080,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;
Expand Down Expand Up @@ -1758,10 +1763,11 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
let mut v = map.into_values().collect::<Vec<_>>();
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!("{}", ix);
NodeValue::FootnoteDefinition(ref mut nfd) => {
nfd.name = f.name.to_string();
nfd.total_references = f.total_references;
}
_ => unreachable!(),
}
Expand All @@ -1776,11 +1782,16 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
map: &mut HashMap<String, FootnoteDefinition<'a>>,
) {
match node.data.borrow().value {
NodeValue::FootnoteDefinition(ref name) => {
NodeValue::FootnoteDefinition(ref nfd) => {
node.detach();
map.insert(
strings::normalize_label(name),
FootnoteDefinition { ix: None, node },
strings::normalize_label(&nfd.name),
FootnoteDefinition {
ix: None,
node,
name: strings::normalize_label(&nfd.name),
total_references: 0,
},
);
}
_ => {
Expand All @@ -1799,8 +1810,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) => {
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 => {
Expand All @@ -1809,9 +1820,11 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> {
*ixp
}
};
*name = format!("{}", ix);
footnote.total_references += 1;
nfr.ref_num = footnote.total_references;
nfr.ix = ix;
} else {
replace = Some(name.clone());
replace = Some(nfr.name.clone());
}
}
_ => {
Expand Down
10 changes: 6 additions & 4 deletions src/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<nodes::TableAlignment> = aligns;
Expand Down Expand Up @@ -207,8 +208,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;
}
}
}
Loading