render footnotes
This commit is contained in:
parent
cbead322ed
commit
8ccfb4c603
5 changed files with 406 additions and 14 deletions
64
src/block.rs
64
src/block.rs
|
@ -618,6 +618,32 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_footnote_post() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"[^a]\n",
|
||||||
|
"\n",
|
||||||
|
"[^a]: note\n",
|
||||||
|
"\n",
|
||||||
|
"para\n", //
|
||||||
|
),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "[^a]"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Enter(Container(Footnote)), "a"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "note"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
(Atom(Blankline), "\n"),
|
||||||
|
(Exit(Container(Footnote)), "a"),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "para"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_attr() {
|
fn parse_attr() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
|
@ -754,4 +780,42 @@ mod test {
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_footnote_empty() {
|
||||||
|
test_block!("[^tag]:\n", Block::Container(Footnote), "tag", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_footnote_single() {
|
||||||
|
test_block!("[^tag]: a\n", Block::Container(Footnote), "tag", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_footnote_multiline() {
|
||||||
|
test_block!(
|
||||||
|
concat!(
|
||||||
|
"[^tag]: a\n",
|
||||||
|
" b\n", //
|
||||||
|
),
|
||||||
|
Block::Container(Footnote),
|
||||||
|
"tag",
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_footnote_multiline_post() {
|
||||||
|
test_block!(
|
||||||
|
concat!(
|
||||||
|
"[^tag]: a\n",
|
||||||
|
" b\n",
|
||||||
|
"\n",
|
||||||
|
"para\n", //
|
||||||
|
),
|
||||||
|
Block::Container(Footnote),
|
||||||
|
"tag",
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
67
src/html.rs
67
src/html.rs
|
@ -48,25 +48,31 @@ enum Raw {
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Writer<I, W> {
|
struct Writer<I: Iterator, W> {
|
||||||
events: I,
|
events: std::iter::Peekable<I>,
|
||||||
out: W,
|
out: W,
|
||||||
raw: Raw,
|
raw: Raw,
|
||||||
text_only: bool,
|
text_only: bool,
|
||||||
|
encountered_footnote: bool,
|
||||||
|
footnote_number: Option<std::num::NonZeroUsize>,
|
||||||
|
footnote_backlink_written: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
||||||
fn new(events: I, out: W) -> Self {
|
fn new(events: I, out: W) -> Self {
|
||||||
Self {
|
Self {
|
||||||
events,
|
events: events.peekable(),
|
||||||
out,
|
out,
|
||||||
raw: Raw::None,
|
raw: Raw::None,
|
||||||
text_only: false,
|
text_only: false,
|
||||||
|
encountered_footnote: false,
|
||||||
|
footnote_number: None,
|
||||||
|
footnote_backlink_written: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self) -> std::fmt::Result {
|
fn write(&mut self) -> std::fmt::Result {
|
||||||
for e in &mut self.events {
|
while let Some(e) = self.events.next() {
|
||||||
match e {
|
match e {
|
||||||
Event::Start(c, attrs) => {
|
Event::Start(c, attrs) => {
|
||||||
if c.is_block() {
|
if c.is_block() {
|
||||||
|
@ -81,7 +87,18 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
||||||
Container::ListItem => self.out.write_str("<li")?,
|
Container::ListItem => self.out.write_str("<li")?,
|
||||||
Container::DescriptionList => self.out.write_str("<dl")?,
|
Container::DescriptionList => self.out.write_str("<dl")?,
|
||||||
Container::DescriptionDetails => self.out.write_str("<dd")?,
|
Container::DescriptionDetails => self.out.write_str("<dd")?,
|
||||||
Container::Footnote { .. } => todo!(),
|
Container::Footnote { number, .. } => {
|
||||||
|
assert!(self.footnote_number.is_none());
|
||||||
|
self.footnote_number = Some((*number).try_into().unwrap());
|
||||||
|
if !self.encountered_footnote {
|
||||||
|
self.encountered_footnote = true;
|
||||||
|
self.out
|
||||||
|
.write_str("<section role=\"doc-endnotes\">\n<hr>\n<ol>\n")?;
|
||||||
|
}
|
||||||
|
write!(self.out, "<li id=\"fn{}\">", number)?;
|
||||||
|
self.footnote_backlink_written = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Container::Table => self.out.write_str("<table")?,
|
Container::Table => self.out.write_str("<table")?,
|
||||||
Container::TableRow => self.out.write_str("<tr")?,
|
Container::TableRow => self.out.write_str("<tr")?,
|
||||||
Container::Div { .. } => self.out.write_str("<div")?,
|
Container::Div { .. } => self.out.write_str("<div")?,
|
||||||
|
@ -194,11 +211,36 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
||||||
Container::ListItem => self.out.write_str("</li>")?,
|
Container::ListItem => self.out.write_str("</li>")?,
|
||||||
Container::DescriptionList => self.out.write_str("</dl>")?,
|
Container::DescriptionList => self.out.write_str("</dl>")?,
|
||||||
Container::DescriptionDetails => self.out.write_str("</dd>")?,
|
Container::DescriptionDetails => self.out.write_str("</dd>")?,
|
||||||
Container::Footnote { .. } => todo!(),
|
Container::Footnote { number, .. } => {
|
||||||
|
if !self.footnote_backlink_written {
|
||||||
|
write!(
|
||||||
|
self.out,
|
||||||
|
"\n<p><a href=\"#fnref{}\" role=\"doc-backlink\">↩︎︎</a></p>",
|
||||||
|
number,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
self.out.write_str("\n</li>")?;
|
||||||
|
self.footnote_number = None;
|
||||||
|
}
|
||||||
Container::Table => self.out.write_str("</table>")?,
|
Container::Table => self.out.write_str("</table>")?,
|
||||||
Container::TableRow => self.out.write_str("</tr>")?,
|
Container::TableRow => self.out.write_str("</tr>")?,
|
||||||
Container::Div { .. } => self.out.write_str("</div>")?,
|
Container::Div { .. } => self.out.write_str("</div>")?,
|
||||||
Container::Paragraph => self.out.write_str("</p>")?,
|
Container::Paragraph => {
|
||||||
|
if let Some(num) = self.footnote_number {
|
||||||
|
if matches!(
|
||||||
|
self.events.peek(),
|
||||||
|
Some(Event::End(Container::Footnote { .. }))
|
||||||
|
) {
|
||||||
|
write!(
|
||||||
|
self.out,
|
||||||
|
r##"<a href="#fnref{}" role="doc-backlink">↩︎︎</a>"##,
|
||||||
|
num
|
||||||
|
)?;
|
||||||
|
self.footnote_backlink_written = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.out.write_str("</p>")?;
|
||||||
|
}
|
||||||
Container::Heading { level } => write!(self.out, "</h{}>", level)?,
|
Container::Heading { level } => write!(self.out, "</h{}>", level)?,
|
||||||
Container::TableCell => self.out.write_str("</td>")?,
|
Container::TableCell => self.out.write_str("</td>")?,
|
||||||
Container::DescriptionTerm => self.out.write_str("</dt>")?,
|
Container::DescriptionTerm => self.out.write_str("</dt>")?,
|
||||||
|
@ -268,6 +310,13 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::Atom(a) => match a {
|
Event::Atom(a) => match a {
|
||||||
|
Atom::FootnoteReference(_tag, number) => {
|
||||||
|
write!(
|
||||||
|
self.out,
|
||||||
|
r##"<a id="fnref{}" href="#fn{}" role="doc-noteref"><sup>{}</sup></a>"##,
|
||||||
|
number, number, number
|
||||||
|
)?;
|
||||||
|
}
|
||||||
Atom::Ellipsis => self.out.write_str("…")?,
|
Atom::Ellipsis => self.out.write_str("…")?,
|
||||||
Atom::EnDash => self.out.write_str("–")?,
|
Atom::EnDash => self.out.write_str("–")?,
|
||||||
Atom::EmDash => self.out.write_str("—")?,
|
Atom::EmDash => self.out.write_str("—")?,
|
||||||
|
@ -279,6 +328,10 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.encountered_footnote {
|
||||||
|
self.out.write_str("\n</ol>\n</section>")?;
|
||||||
|
}
|
||||||
|
self.out.write_char('\n')?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use Container::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Atom {
|
pub enum Atom {
|
||||||
|
FootnoteReference,
|
||||||
Softbreak,
|
Softbreak,
|
||||||
Hardbreak,
|
Hardbreak,
|
||||||
Escape,
|
Escape,
|
||||||
|
@ -111,6 +112,7 @@ impl<I: Iterator<Item = char> + Clone> Parser<I> {
|
||||||
self.parse_verbatim(&first)
|
self.parse_verbatim(&first)
|
||||||
.or_else(|| self.parse_attributes(&first))
|
.or_else(|| self.parse_attributes(&first))
|
||||||
.or_else(|| self.parse_autolink(&first))
|
.or_else(|| self.parse_autolink(&first))
|
||||||
|
.or_else(|| self.parse_footnote_reference(&first))
|
||||||
.or_else(|| self.parse_container(&first))
|
.or_else(|| self.parse_container(&first))
|
||||||
.or_else(|| self.parse_atom(&first))
|
.or_else(|| self.parse_atom(&first))
|
||||||
.unwrap_or(Event {
|
.unwrap_or(Event {
|
||||||
|
@ -341,6 +343,52 @@ impl<I: Iterator<Item = char> + Clone> Parser<I> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_footnote_reference(&mut self, first: &lex::Token) -> Option<Event> {
|
||||||
|
if first.kind == lex::Kind::Open(Delimiter::Bracket)
|
||||||
|
&& matches!(
|
||||||
|
self.peek(),
|
||||||
|
Some(lex::Token {
|
||||||
|
kind: lex::Kind::Sym(Symbol::Caret),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let tok = self.eat();
|
||||||
|
debug_assert_eq!(
|
||||||
|
tok,
|
||||||
|
Some(lex::Token {
|
||||||
|
kind: lex::Kind::Sym(Symbol::Caret),
|
||||||
|
len: 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let mut ahead = self.lexer.chars();
|
||||||
|
let mut end = false;
|
||||||
|
let len = (&mut ahead)
|
||||||
|
.take_while(|c| {
|
||||||
|
if *c == '[' {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if *c == ']' {
|
||||||
|
end = true;
|
||||||
|
};
|
||||||
|
!end && *c != '\n'
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
end.then(|| {
|
||||||
|
self.lexer = lex::Lexer::new(ahead);
|
||||||
|
self.span = Span::by_len(self.span.end(), len);
|
||||||
|
let ev = Event {
|
||||||
|
kind: EventKind::Atom(FootnoteReference),
|
||||||
|
span: self.span,
|
||||||
|
};
|
||||||
|
self.span = Span::by_len(self.span.end(), 1);
|
||||||
|
ev
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_container(&mut self, first: &lex::Token) -> Option<Event> {
|
fn parse_container(&mut self, first: &lex::Token) -> Option<Event> {
|
||||||
Delim::from_token(first.kind).map(|(delim, dir)| {
|
Delim::from_token(first.kind).map(|(delim, dir)| {
|
||||||
self.openers
|
self.openers
|
||||||
|
@ -633,6 +681,7 @@ impl<I: Iterator<Item = char> + Clone> Iterator for Parser<I> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::Atom::*;
|
||||||
use super::Container::*;
|
use super::Container::*;
|
||||||
use super::EventKind::*;
|
use super::EventKind::*;
|
||||||
use super::Verbatim;
|
use super::Verbatim;
|
||||||
|
@ -928,6 +977,16 @@ mod test {
|
||||||
test_parse!("<not-a-url>", (Str, "<not-a-url>"));
|
test_parse!("<not-a-url>", (Str, "<not-a-url>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footnote_reference() {
|
||||||
|
test_parse!(
|
||||||
|
"text[^footnote]. more text",
|
||||||
|
(Str, "text"),
|
||||||
|
(Atom(FootnoteReference), "footnote"),
|
||||||
|
(Str, ". more text"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn container_basic() {
|
fn container_basic() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
|
|
220
src/lib.rs
220
src/lib.rs
|
@ -25,7 +25,7 @@ pub enum Event<'s> {
|
||||||
/// A string object, text only.
|
/// A string object, text only.
|
||||||
Str(CowStr<'s>),
|
Str(CowStr<'s>),
|
||||||
/// An atomic element.
|
/// An atomic element.
|
||||||
Atom(Atom),
|
Atom(Atom<'s>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -41,7 +41,7 @@ pub enum Container<'s> {
|
||||||
/// Details describing a term within a description list.
|
/// Details describing a term within a description list.
|
||||||
DescriptionDetails,
|
DescriptionDetails,
|
||||||
/// A footnote definition.
|
/// A footnote definition.
|
||||||
Footnote { tag: &'s str },
|
Footnote { tag: &'s str, number: usize },
|
||||||
/// A table element.
|
/// A table element.
|
||||||
Table,
|
Table,
|
||||||
/// A row element of a table.
|
/// A row element of a table.
|
||||||
|
@ -212,7 +212,9 @@ pub enum OrderedListStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Atom {
|
pub enum Atom<'s> {
|
||||||
|
/// A footnote reference.
|
||||||
|
FootnoteReference(&'s str, usize),
|
||||||
/// A horizontal ellipsis, i.e. a set of three periods.
|
/// A horizontal ellipsis, i.e. a set of three periods.
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
/// An en dash.
|
/// An en dash.
|
||||||
|
@ -257,7 +259,7 @@ impl<'s> Container<'s> {
|
||||||
match c {
|
match c {
|
||||||
block::Container::Blockquote => Self::Blockquote,
|
block::Container::Blockquote => Self::Blockquote,
|
||||||
block::Container::Div => panic!(),
|
block::Container::Div => panic!(),
|
||||||
block::Container::Footnote => Self::Footnote { tag: content },
|
block::Container::Footnote => panic!(),
|
||||||
block::Container::ListItem => todo!(),
|
block::Container::ListItem => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,6 +274,14 @@ pub struct Parser<'s> {
|
||||||
tree: block::Branch,
|
tree: block::Branch,
|
||||||
inlines: span::InlineSpans<'s>,
|
inlines: span::InlineSpans<'s>,
|
||||||
inline_parser: Option<inline::Parser<span::InlineCharsIter<'s>>>,
|
inline_parser: Option<inline::Parser<span::InlineCharsIter<'s>>>,
|
||||||
|
/// Footnote references in the order they were encountered, without duplicates.
|
||||||
|
footnote_references: Vec<&'s str>,
|
||||||
|
/// Cache of footnotes to emit at the end.
|
||||||
|
footnotes: std::collections::HashMap<&'s str, block::Branch>,
|
||||||
|
/// Next or current footnote being parsed and emitted.
|
||||||
|
footnote_index: usize,
|
||||||
|
/// Currently within a footnote.
|
||||||
|
footnote_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Parser<'s> {
|
impl<'s> Parser<'s> {
|
||||||
|
@ -305,6 +315,10 @@ impl<'s> Parser<'s> {
|
||||||
_tree_data: tree,
|
_tree_data: tree,
|
||||||
link_definitions,
|
link_definitions,
|
||||||
tree: branch,
|
tree: branch,
|
||||||
|
footnote_references: Vec::new(),
|
||||||
|
footnotes: std::collections::HashMap::new(),
|
||||||
|
footnote_index: 0,
|
||||||
|
footnote_active: false,
|
||||||
inlines: span::InlineSpans::new(src),
|
inlines: span::InlineSpans::new(src),
|
||||||
inline_parser: None,
|
inline_parser: None,
|
||||||
}
|
}
|
||||||
|
@ -389,6 +403,30 @@ impl<'s> Parser<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline::EventKind::Atom(a) => Event::Atom(match a {
|
inline::EventKind::Atom(a) => Event::Atom(match a {
|
||||||
|
inline::Atom::FootnoteReference => {
|
||||||
|
let tag = match self.inlines.src(inline.span) {
|
||||||
|
CowStr::Borrowed(s) => s,
|
||||||
|
CowStr::Owned(..) => panic!(),
|
||||||
|
};
|
||||||
|
let number = self
|
||||||
|
.footnote_references
|
||||||
|
.iter()
|
||||||
|
.position(|t| *t == tag)
|
||||||
|
.map_or_else(
|
||||||
|
|| {
|
||||||
|
self.footnote_references.push(tag);
|
||||||
|
self.footnote_references.len()
|
||||||
|
},
|
||||||
|
|i| i + 1,
|
||||||
|
);
|
||||||
|
Atom::FootnoteReference(
|
||||||
|
match self.inlines.src(inline.span) {
|
||||||
|
CowStr::Borrowed(s) => s,
|
||||||
|
CowStr::Owned(..) => panic!(),
|
||||||
|
},
|
||||||
|
number,
|
||||||
|
)
|
||||||
|
}
|
||||||
inline::Atom::Ellipsis => Atom::Ellipsis,
|
inline::Atom::Ellipsis => Atom::Ellipsis,
|
||||||
inline::Atom::EnDash => Atom::EnDash,
|
inline::Atom::EnDash => Atom::EnDash,
|
||||||
inline::Atom::EmDash => Atom::EmDash,
|
inline::Atom::EmDash => Atom::EmDash,
|
||||||
|
@ -439,6 +477,10 @@ impl<'s> Parser<'s> {
|
||||||
block::Container::Div { .. } => Container::Div {
|
block::Container::Div { .. } => Container::Div {
|
||||||
class: (!ev.span.is_empty()).then(|| content),
|
class: (!ev.span.is_empty()).then(|| content),
|
||||||
},
|
},
|
||||||
|
block::Container::Footnote => {
|
||||||
|
self.footnotes.insert(content, self.tree.take_branch());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
_ => Container::from_container_block(content, c),
|
_ => Container::from_container_block(content, c),
|
||||||
};
|
};
|
||||||
Event::Start(container, attributes)
|
Event::Start(container, attributes)
|
||||||
|
@ -456,13 +498,43 @@ impl<'s> Parser<'s> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn footnote(&mut self) -> Option<Event<'s>> {
|
||||||
|
if self.footnote_active {
|
||||||
|
let tag = self.footnote_references.get(self.footnote_index).unwrap();
|
||||||
|
self.footnote_index += 1;
|
||||||
|
self.footnote_active = false;
|
||||||
|
Some(Event::End(Container::Footnote {
|
||||||
|
tag,
|
||||||
|
number: self.footnote_index,
|
||||||
|
}))
|
||||||
|
} else if let Some(tag) = self.footnote_references.get(self.footnote_index) {
|
||||||
|
self.tree = self
|
||||||
|
.footnotes
|
||||||
|
.remove(tag)
|
||||||
|
.unwrap_or_else(block::Branch::empty);
|
||||||
|
self.footnote_active = true;
|
||||||
|
|
||||||
|
Some(Event::Start(
|
||||||
|
Container::Footnote {
|
||||||
|
tag,
|
||||||
|
number: self.footnote_index + 1,
|
||||||
|
},
|
||||||
|
Attributes::new(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Iterator for Parser<'s> {
|
impl<'s> Iterator for Parser<'s> {
|
||||||
type Item = Event<'s>;
|
type Item = Event<'s>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.inline().or_else(|| self.block())
|
self.inline()
|
||||||
|
.or_else(|| self.block())
|
||||||
|
.or_else(|| self.footnote())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,6 +802,144 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footnote_references() {
|
||||||
|
test_parse!(
|
||||||
|
"[^a][^b][^c]",
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Atom(FootnoteReference("a", 1)),
|
||||||
|
Atom(FootnoteReference("b", 2)),
|
||||||
|
Atom(FootnoteReference("c", 3)),
|
||||||
|
End(Paragraph),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
}),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "b",
|
||||||
|
number: 2
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "b",
|
||||||
|
number: 2
|
||||||
|
}),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "c",
|
||||||
|
number: 3
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "c",
|
||||||
|
number: 3
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footnote() {
|
||||||
|
test_parse!(
|
||||||
|
"[^a]\n\n[^a]: a\n",
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Atom(FootnoteReference("a", 1)),
|
||||||
|
End(Paragraph),
|
||||||
|
Atom(Blankline),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Str("a".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footnote_multiblock() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"[^a]\n",
|
||||||
|
"\n",
|
||||||
|
"[^a]: abc\n",
|
||||||
|
"\n",
|
||||||
|
" def", //
|
||||||
|
),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Atom(FootnoteReference("a", 1)),
|
||||||
|
End(Paragraph),
|
||||||
|
Atom(Blankline),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Str("abc".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
Atom(Blankline),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Str("def".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn footnote_post() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"[^a]\n",
|
||||||
|
"\n",
|
||||||
|
"[^a]: note\n",
|
||||||
|
"para\n", //
|
||||||
|
),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Atom(FootnoteReference("a", 1)),
|
||||||
|
End(Paragraph),
|
||||||
|
Atom(Blankline),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Str("para".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
Start(
|
||||||
|
Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
},
|
||||||
|
Attributes::new()
|
||||||
|
),
|
||||||
|
Start(Paragraph, Attributes::new()),
|
||||||
|
Str("note".into()),
|
||||||
|
End(Paragraph),
|
||||||
|
End(Footnote {
|
||||||
|
tag: "a",
|
||||||
|
number: 1
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attr_block() {
|
fn attr_block() {
|
||||||
test_parse!(
|
test_parse!(
|
||||||
|
|
10
src/tree.rs
10
src/tree.rs
|
@ -50,6 +50,14 @@ pub struct Branch<C: 'static, A: 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, A> Branch<C, A> {
|
impl<C, A> Branch<C, A> {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
nodes: &[],
|
||||||
|
branch: Vec::new(),
|
||||||
|
head: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Count number of direct children nodes.
|
/// Count number of direct children nodes.
|
||||||
pub fn count_children(&self) -> usize {
|
pub fn count_children(&self) -> usize {
|
||||||
let mut head = self.head;
|
let mut head = self.head;
|
||||||
|
@ -62,8 +70,6 @@ impl<C, A> Branch<C, A> {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split off the remaining part of the current branch. The returned [`Branch`] will continue on
|
|
||||||
/// the branch, this [`Branch`] will skip over the current branch.
|
|
||||||
pub fn take_branch(&mut self) -> Self {
|
pub fn take_branch(&mut self) -> Self {
|
||||||
let head = self.head.take();
|
let head = self.head.take();
|
||||||
self.head = self.branch.pop();
|
self.head = self.branch.pop();
|
||||||
|
|
Loading…
Reference in a new issue