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…
	
	Add table
		Add a link
		
	
		Reference in a new issue