block: parse tables
This commit is contained in:
parent
6ec5b09367
commit
c288264aee
4 changed files with 355 additions and 42 deletions
255
src/block.rs
255
src/block.rs
|
@ -1,9 +1,11 @@
|
||||||
|
use crate::Alignment;
|
||||||
use crate::OrderedListNumbering::*;
|
use crate::OrderedListNumbering::*;
|
||||||
use crate::OrderedListStyle::*;
|
use crate::OrderedListStyle::*;
|
||||||
use crate::Span;
|
use crate::Span;
|
||||||
use crate::EOF;
|
use crate::EOF;
|
||||||
|
|
||||||
use crate::attr;
|
use crate::attr;
|
||||||
|
use crate::lex;
|
||||||
use crate::tree;
|
use crate::tree;
|
||||||
|
|
||||||
use Atom::*;
|
use Atom::*;
|
||||||
|
@ -59,9 +61,9 @@ pub enum Leaf {
|
||||||
/// Each inline is a line.
|
/// Each inline is a line.
|
||||||
Heading,
|
Heading,
|
||||||
|
|
||||||
/// Span is first `|` character.
|
/// Span is '|'.
|
||||||
/// Each inline is a line (row).
|
/// Has zero or one inline for the cell contents.
|
||||||
Table,
|
TableCell(Alignment),
|
||||||
|
|
||||||
/// Span is the link tag.
|
/// Span is the link tag.
|
||||||
/// Inlines are lines of the URL.
|
/// Inlines are lines of the URL.
|
||||||
|
@ -91,6 +93,12 @@ pub enum Container {
|
||||||
|
|
||||||
/// Span is footnote tag.
|
/// Span is footnote tag.
|
||||||
Footnote,
|
Footnote,
|
||||||
|
|
||||||
|
/// Span is empty, before first '|' character.
|
||||||
|
Table,
|
||||||
|
|
||||||
|
/// Span is first '|' character.
|
||||||
|
TableRow { head: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -121,6 +129,8 @@ struct TreeParser<'s> {
|
||||||
prev_blankline: bool,
|
prev_blankline: bool,
|
||||||
/// Stack of currently open lists.
|
/// Stack of currently open lists.
|
||||||
open_lists: Vec<OpenList>,
|
open_lists: Vec<OpenList>,
|
||||||
|
/// Alignments for each column in for the current table.
|
||||||
|
alignments: Vec<Alignment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> TreeParser<'s> {
|
impl<'s> TreeParser<'s> {
|
||||||
|
@ -131,6 +141,7 @@ impl<'s> TreeParser<'s> {
|
||||||
tree: TreeBuilder::new(),
|
tree: TreeBuilder::new(),
|
||||||
prev_blankline: false,
|
prev_blankline: false,
|
||||||
open_lists: Vec::new(),
|
open_lists: Vec::new(),
|
||||||
|
alignments: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +264,134 @@ impl<'s> TreeParser<'s> {
|
||||||
lines.iter().for_each(|line| self.tree.inline(*line));
|
lines.iter().for_each(|line| self.tree.inline(*line));
|
||||||
self.tree.exit();
|
self.tree.exit();
|
||||||
}
|
}
|
||||||
|
Block::Container(Table) => {
|
||||||
|
self.alignments.clear();
|
||||||
|
self.tree.enter(Node::Container(Table), span);
|
||||||
|
let mut last_row_node = None;
|
||||||
|
for row in lines {
|
||||||
|
let row_node = self
|
||||||
|
.tree
|
||||||
|
.enter(Node::Container(TableRow { head: false }), row.with_len(1));
|
||||||
|
let rem = row.skip(1);
|
||||||
|
let lex = lex::Lexer::new(row.skip(1).of(self.src).chars());
|
||||||
|
let mut pos = rem.start();
|
||||||
|
let mut cell_start = pos;
|
||||||
|
let mut separator_row = true;
|
||||||
|
let mut verbatim = None;
|
||||||
|
let mut column_index = 0;
|
||||||
|
for lex::Token { kind, len } in lex {
|
||||||
|
if let Some(l) = verbatim {
|
||||||
|
if matches!(kind, lex::Kind::Seq(lex::Sequence::Backtick))
|
||||||
|
&& len == l
|
||||||
|
{
|
||||||
|
verbatim = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match kind {
|
||||||
|
lex::Kind::Sym(lex::Symbol::Pipe) => {
|
||||||
|
{
|
||||||
|
let span =
|
||||||
|
Span::new(cell_start, pos).trim(self.src);
|
||||||
|
let cell = span.of(self.src);
|
||||||
|
let separator_cell = match cell.len() {
|
||||||
|
0 => false,
|
||||||
|
1 => cell == "-",
|
||||||
|
2 => matches!(cell, ":-" | "--" | "-:"),
|
||||||
|
l => {
|
||||||
|
matches!(cell.as_bytes()[0], b'-' | b':')
|
||||||
|
&& matches!(
|
||||||
|
cell.as_bytes()[l - 1],
|
||||||
|
b'-' | b':'
|
||||||
|
)
|
||||||
|
&& cell
|
||||||
|
.chars()
|
||||||
|
.skip(1)
|
||||||
|
.take(l - 2)
|
||||||
|
.all(|c| c == '-')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
separator_row &= separator_cell;
|
||||||
|
self.tree.enter(
|
||||||
|
Node::Leaf(TableCell(
|
||||||
|
self.alignments
|
||||||
|
.get(column_index)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(Alignment::Unspecified),
|
||||||
|
)),
|
||||||
|
Span::by_len(cell_start - 1, 1),
|
||||||
|
);
|
||||||
|
self.tree.inline(span);
|
||||||
|
self.tree.exit(); // cell
|
||||||
|
cell_start = pos + len;
|
||||||
|
column_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lex::Kind::Seq(lex::Sequence::Backtick) => {
|
||||||
|
verbatim = Some(len);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
if separator_row {
|
||||||
|
self.alignments.clear();
|
||||||
|
self.alignments.extend(
|
||||||
|
self.tree
|
||||||
|
.children(row_node)
|
||||||
|
.filter(|(kind, _)| matches!(kind, tree::Element::Inline))
|
||||||
|
.map(|(_, sp)| {
|
||||||
|
let cell = sp.of(self.src);
|
||||||
|
let l = cell.as_bytes()[0] == b':';
|
||||||
|
let r = cell.as_bytes()[cell.len() - 1] == b':';
|
||||||
|
match (l, r) {
|
||||||
|
(false, false) => Alignment::Unspecified,
|
||||||
|
(false, true) => Alignment::Right,
|
||||||
|
(true, false) => Alignment::Left,
|
||||||
|
(true, true) => Alignment::Center,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
self.tree.exit_discard(); // table row
|
||||||
|
if let Some(head_row) = last_row_node {
|
||||||
|
self.tree
|
||||||
|
.children(head_row)
|
||||||
|
.filter(|(e, _sp)| {
|
||||||
|
matches!(
|
||||||
|
e,
|
||||||
|
tree::Element::Container(Node::Leaf(TableCell(..)))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.zip(
|
||||||
|
self.alignments
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain(std::iter::repeat(Alignment::Unspecified)),
|
||||||
|
)
|
||||||
|
.for_each(|((e, _), new_align)| {
|
||||||
|
if let tree::Element::Container(Node::Leaf(
|
||||||
|
TableCell(alignment),
|
||||||
|
)) = e
|
||||||
|
{
|
||||||
|
*alignment = new_align;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let tree::Element::Container(Node::Container(TableRow {
|
||||||
|
head,
|
||||||
|
})) = self.tree.elem(head_row)
|
||||||
|
{
|
||||||
|
*head = true;
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.tree.exit(); // table row
|
||||||
|
}
|
||||||
|
last_row_node = Some(row_node);
|
||||||
|
}
|
||||||
|
self.tree.exit(); // table
|
||||||
|
}
|
||||||
Block::Container(c) => {
|
Block::Container(c) => {
|
||||||
let line_count_inner = lines.len() - usize::from(matches!(c, Div));
|
let line_count_inner = lines.len() - usize::from(matches!(c, Div));
|
||||||
|
|
||||||
|
@ -270,7 +409,9 @@ impl<'s> TreeParser<'s> {
|
||||||
let skip = match c {
|
let skip = match c {
|
||||||
Blockquote => spaces + "> ".len(),
|
Blockquote => spaces + "> ".len(),
|
||||||
ListItem(..) | Footnote | Div => spaces.min(indent),
|
ListItem(..) | Footnote | Div => spaces.min(indent),
|
||||||
List { .. } | DescriptionList => panic!(),
|
List { .. } | DescriptionList | Table | TableRow { .. } => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let len = sp.len() - usize::from(sp.of(self.src).ends_with('\n'));
|
let len = sp.len() - usize::from(sp.of(self.src).ends_with('\n'));
|
||||||
*sp = sp.skip(skip.min(len));
|
*sp = sp.skip(skip.min(len));
|
||||||
|
@ -381,9 +522,12 @@ impl BlockParser {
|
||||||
}
|
}
|
||||||
'{' => (attr::valid(line_t.chars()).0 == line_t.trim_end().len())
|
'{' => (attr::valid(line_t.chars()).0 == line_t.trim_end().len())
|
||||||
.then(|| (Block::Atom(Attributes), Span::by_len(start, line_t.len()))),
|
.then(|| (Block::Atom(Attributes), Span::by_len(start, line_t.len()))),
|
||||||
'|' => (&line_t[line_t.len() - 1..] == "|"
|
'|' => {
|
||||||
&& &line_t[line_t.len() - 2..line_t.len() - 1] != "\\")
|
let l = line_t.trim_end().len();
|
||||||
.then(|| (Block::Leaf(Table), Span::by_len(start, 1))),
|
// FIXME: last byte may be pipe but end of prefixed unicode char
|
||||||
|
(line_t.as_bytes()[l - 1] == b'|' && line_t.as_bytes()[l - 2] != b'\\')
|
||||||
|
.then(|| (Block::Container(Table), Span::empty_at(start)))
|
||||||
|
}
|
||||||
'[' => chars.as_str().find("]:").map(|l| {
|
'[' => chars.as_str().find("]:").map(|l| {
|
||||||
let tag = &chars.as_str()[0..l];
|
let tag = &chars.as_str()[0..l];
|
||||||
let (tag, is_footnote) = if let Some(tag) = tag.strip_prefix('^') {
|
let (tag, is_footnote) = if let Some(tag) = tag.strip_prefix('^') {
|
||||||
|
@ -472,7 +616,7 @@ impl BlockParser {
|
||||||
let empty = line_t.is_empty();
|
let empty = line_t.is_empty();
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Block::Atom(..) => false,
|
Block::Atom(..) => false,
|
||||||
Block::Leaf(Paragraph | Heading | Table) => !line.trim().is_empty(),
|
Block::Leaf(Paragraph | Heading) => !line.trim().is_empty(),
|
||||||
Block::Leaf(LinkDefinition) => line.starts_with(' ') && !line.trim().is_empty(),
|
Block::Leaf(LinkDefinition) => line.starts_with(' ') && !line.trim().is_empty(),
|
||||||
Block::Container(Blockquote) => line.trim().starts_with('>'),
|
Block::Container(Blockquote) => line.trim().starts_with('>'),
|
||||||
Block::Container(ListItem(..)) => {
|
Block::Container(ListItem(..)) => {
|
||||||
|
@ -494,7 +638,15 @@ impl BlockParser {
|
||||||
!((&mut c).take(fence_length).all(|c| c == fence)
|
!((&mut c).take(fence_length).all(|c| c == fence)
|
||||||
&& c.next().map_or(true, char::is_whitespace))
|
&& c.next().map_or(true, char::is_whitespace))
|
||||||
}
|
}
|
||||||
Block::Container(List { .. } | DescriptionList) => panic!(),
|
Block::Container(List { .. } | DescriptionList | TableRow { .. })
|
||||||
|
| Block::Leaf(TableCell(..)) => {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
Block::Container(Table) => {
|
||||||
|
let line = line.trim();
|
||||||
|
let l = line.len();
|
||||||
|
line.as_bytes()[l - 1] == b'|' && line.as_bytes()[l - 2] != b'\\'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,6 +767,7 @@ fn lines(src: &str) -> impl Iterator<Item = Span> + '_ {
|
||||||
mod test {
|
mod test {
|
||||||
use crate::tree::EventKind;
|
use crate::tree::EventKind;
|
||||||
use crate::tree::EventKind::*;
|
use crate::tree::EventKind::*;
|
||||||
|
use crate::Alignment;
|
||||||
use crate::OrderedListNumbering::*;
|
use crate::OrderedListNumbering::*;
|
||||||
use crate::OrderedListStyle::*;
|
use crate::OrderedListStyle::*;
|
||||||
|
|
||||||
|
@ -1242,6 +1395,90 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_table() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"|a|b|c|\n", //
|
||||||
|
"|-|-|-|\n", //
|
||||||
|
"|1|2|3|\n", //
|
||||||
|
),
|
||||||
|
(Enter(Container(Table)), ""),
|
||||||
|
(Enter(Container(TableRow { head: true })), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "a"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "b"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "c"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Exit(Container(TableRow { head: true })), "|"),
|
||||||
|
(Enter(Container(TableRow { head: false })), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "1"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "2"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "3"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Exit(Container(TableRow { head: false })), "|"),
|
||||||
|
(Exit(Container(Table)), "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_table_post() {
|
||||||
|
test_parse!(
|
||||||
|
"|a|\npara",
|
||||||
|
(Enter(Container(Table)), ""),
|
||||||
|
(Enter(Container(TableRow { head: false })), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Inline, "a"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Unspecified))), "|"),
|
||||||
|
(Exit(Container(TableRow { head: false })), "|"),
|
||||||
|
(Exit(Container(Table)), ""),
|
||||||
|
(Enter(Leaf(Paragraph)), ""),
|
||||||
|
(Inline, "para"),
|
||||||
|
(Exit(Leaf(Paragraph)), ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_table_align() {
|
||||||
|
test_parse!(
|
||||||
|
concat!(
|
||||||
|
"|:---|:----:|----:|\n",
|
||||||
|
"|left|center|right|\n", //
|
||||||
|
),
|
||||||
|
(Enter(Container(Table)), ""),
|
||||||
|
(Enter(Container(TableRow { head: false })), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Left))), "|"),
|
||||||
|
(Inline, "left"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Left))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Center))), "|"),
|
||||||
|
(Inline, "center"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Center))), "|"),
|
||||||
|
(Enter(Leaf(TableCell(Alignment::Right))), "|"),
|
||||||
|
(Inline, "right"),
|
||||||
|
(Exit(Leaf(TableCell(Alignment::Right))), "|"),
|
||||||
|
(Exit(Container(TableRow { head: false })), "|"),
|
||||||
|
(Exit(Container(Table)), "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_table_sep_row_only() {
|
||||||
|
test_parse!(
|
||||||
|
"|-|-|",
|
||||||
|
(Enter(Container(Table)), ""),
|
||||||
|
(Exit(Container(Table)), "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! test_block {
|
macro_rules! test_block {
|
||||||
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
($src:expr, $kind:expr, $str:expr, $len:expr $(,)?) => {
|
||||||
let lines = super::lines($src).map(|sp| sp.of($src));
|
let lines = super::lines($src).map(|sp| sp.of($src));
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
continue;
|
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")?,
|
||||||
Container::Paragraph => {
|
Container::Paragraph => {
|
||||||
if matches!(self.list_tightness.last(), Some(true)) {
|
if matches!(self.list_tightness.last(), Some(true)) {
|
||||||
|
@ -154,7 +154,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
self.out.write_str("<p")?;
|
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")?,
|
||||||
Container::CodeBlock { .. } => self.out.write_str("<pre")?,
|
Container::CodeBlock { .. } => self.out.write_str("<pre")?,
|
||||||
Container::Span | Container::Math { .. } => self.out.write_str("<span")?,
|
Container::Span | Container::Math { .. } => self.out.write_str("<span")?,
|
||||||
|
@ -301,7 +301,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
self.footnote_number = None;
|
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 => {
|
Container::Paragraph => {
|
||||||
if matches!(self.list_tightness.last(), Some(true)) {
|
if matches!(self.list_tightness.last(), Some(true)) {
|
||||||
|
@ -323,7 +323,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
|
||||||
self.out.write_str("</p>")?;
|
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>")?,
|
||||||
Container::CodeBlock { .. } => self.out.write_str("</code></pre>")?,
|
Container::CodeBlock { .. } => self.out.write_str("</code></pre>")?,
|
||||||
Container::Span => self.out.write_str("</span>")?,
|
Container::Span => self.out.write_str("</span>")?,
|
||||||
|
|
72
src/lib.rs
72
src/lib.rs
|
@ -47,7 +47,7 @@ pub enum Container<'s> {
|
||||||
/// A table element.
|
/// A table element.
|
||||||
Table,
|
Table,
|
||||||
/// A row element of a table.
|
/// A row element of a table.
|
||||||
TableRow,
|
TableRow { head: bool },
|
||||||
/// A block-level divider element.
|
/// A block-level divider element.
|
||||||
Div { class: Option<&'s str> },
|
Div { class: Option<&'s str> },
|
||||||
/// A paragraph.
|
/// A paragraph.
|
||||||
|
@ -55,7 +55,7 @@ pub enum Container<'s> {
|
||||||
/// A heading.
|
/// A heading.
|
||||||
Heading { level: usize },
|
Heading { level: usize },
|
||||||
/// A cell element of row within a table.
|
/// A cell element of row within a table.
|
||||||
TableCell,
|
TableCell { alignment: Alignment, head: bool },
|
||||||
/// A term within a description list.
|
/// A term within a description list.
|
||||||
DescriptionTerm,
|
DescriptionTerm,
|
||||||
/// A block with raw markup for a specific output format.
|
/// A block with raw markup for a specific output format.
|
||||||
|
@ -106,12 +106,12 @@ impl<'s> Container<'s> {
|
||||||
| Self::DescriptionDetails
|
| Self::DescriptionDetails
|
||||||
| Self::Footnote { .. }
|
| Self::Footnote { .. }
|
||||||
| Self::Table
|
| Self::Table
|
||||||
| Self::TableRow
|
| Self::TableRow { .. }
|
||||||
| Self::Div { .. }
|
| Self::Div { .. }
|
||||||
| Self::Paragraph
|
| Self::Paragraph
|
||||||
| Self::Heading { .. }
|
| Self::Heading { .. }
|
||||||
|
| Self::TableCell { .. }
|
||||||
| Self::DescriptionTerm
|
| Self::DescriptionTerm
|
||||||
| Self::TableCell
|
|
||||||
| Self::RawBlock { .. }
|
| Self::RawBlock { .. }
|
||||||
| Self::CodeBlock { .. } => true,
|
| Self::CodeBlock { .. } => true,
|
||||||
Self::Span
|
Self::Span
|
||||||
|
@ -143,11 +143,11 @@ impl<'s> Container<'s> {
|
||||||
| Self::DescriptionDetails
|
| Self::DescriptionDetails
|
||||||
| Self::Footnote { .. }
|
| Self::Footnote { .. }
|
||||||
| Self::Table
|
| Self::Table
|
||||||
| Self::TableRow
|
| Self::TableRow { .. }
|
||||||
| Self::Div { .. } => true,
|
| Self::Div { .. } => true,
|
||||||
Self::Paragraph
|
Self::Paragraph
|
||||||
| Self::Heading { .. }
|
| Self::Heading { .. }
|
||||||
| Self::TableCell
|
| Self::TableCell { .. }
|
||||||
| Self::DescriptionTerm
|
| Self::DescriptionTerm
|
||||||
| Self::RawBlock { .. }
|
| Self::RawBlock { .. }
|
||||||
| Self::CodeBlock { .. }
|
| Self::CodeBlock { .. }
|
||||||
|
@ -170,6 +170,14 @@ impl<'s> Container<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Alignment {
|
||||||
|
Unspecified,
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum SpanLinkType {
|
pub enum SpanLinkType {
|
||||||
Inline,
|
Inline,
|
||||||
|
@ -242,27 +250,6 @@ pub enum Atom<'s> {
|
||||||
Blankline,
|
Blankline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Container<'s> {
|
|
||||||
fn from_leaf_block(content: &'s str, l: block::Leaf) -> Self {
|
|
||||||
match l {
|
|
||||||
block::Leaf::Paragraph => Self::Paragraph,
|
|
||||||
block::Leaf::Heading => Self::Heading {
|
|
||||||
level: content.len(),
|
|
||||||
},
|
|
||||||
block::Leaf::CodeBlock => {
|
|
||||||
if let Some(format) = content.strip_prefix('=') {
|
|
||||||
Self::RawBlock { format }
|
|
||||||
} else {
|
|
||||||
Self::CodeBlock {
|
|
||||||
lang: (!content.is_empty()).then(|| content),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OrderedListNumbering {
|
impl OrderedListNumbering {
|
||||||
fn parse_number(self, n: &str) -> u32 {
|
fn parse_number(self, n: &str) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
|
@ -336,6 +323,8 @@ pub struct Parser<'s> {
|
||||||
/// Inline parser, recreated for each new inline.
|
/// Inline parser, recreated for each new inline.
|
||||||
inline_parser: Option<inline::Parser<span::InlineCharsIter<'s>>>,
|
inline_parser: Option<inline::Parser<span::InlineCharsIter<'s>>>,
|
||||||
|
|
||||||
|
table_head_row: bool,
|
||||||
|
|
||||||
/// Footnote references in the order they were encountered, without duplicates.
|
/// Footnote references in the order they were encountered, without duplicates.
|
||||||
footnote_references: Vec<&'s str>,
|
footnote_references: Vec<&'s str>,
|
||||||
/// Cache of footnotes to emit at the end.
|
/// Cache of footnotes to emit at the end.
|
||||||
|
@ -376,6 +365,7 @@ impl<'s> Parser<'s> {
|
||||||
src,
|
src,
|
||||||
link_definitions,
|
link_definitions,
|
||||||
tree: branch,
|
tree: branch,
|
||||||
|
table_head_row: false,
|
||||||
footnote_references: Vec::new(),
|
footnote_references: Vec::new(),
|
||||||
footnotes: std::collections::HashMap::new(),
|
footnotes: std::collections::HashMap::new(),
|
||||||
footnote_index: 0,
|
footnote_index: 0,
|
||||||
|
@ -533,7 +523,26 @@ impl<'s> Parser<'s> {
|
||||||
self.inline_parser =
|
self.inline_parser =
|
||||||
Some(inline::Parser::new(self.inlines.chars()));
|
Some(inline::Parser::new(self.inlines.chars()));
|
||||||
}
|
}
|
||||||
Container::from_leaf_block(content, l)
|
match l {
|
||||||
|
block::Leaf::Paragraph => Container::Paragraph,
|
||||||
|
block::Leaf::Heading => Container::Heading {
|
||||||
|
level: content.len(),
|
||||||
|
},
|
||||||
|
block::Leaf::CodeBlock => {
|
||||||
|
if let Some(format) = content.strip_prefix('=') {
|
||||||
|
Container::RawBlock { format }
|
||||||
|
} else {
|
||||||
|
Container::CodeBlock {
|
||||||
|
lang: (!content.is_empty()).then(|| content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block::Leaf::TableCell(alignment) => Container::TableCell {
|
||||||
|
alignment,
|
||||||
|
head: self.table_head_row,
|
||||||
|
},
|
||||||
|
block::Leaf::LinkDefinition => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
block::Node::Container(c) => match c {
|
block::Node::Container(c) => match c {
|
||||||
block::Container::Blockquote => Container::Blockquote,
|
block::Container::Blockquote => Container::Blockquote,
|
||||||
|
@ -573,6 +582,13 @@ impl<'s> Parser<'s> {
|
||||||
Container::ListItem
|
Container::ListItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
block::Container::Table => Container::Table,
|
||||||
|
block::Container::TableRow { head } => {
|
||||||
|
if enter {
|
||||||
|
self.table_head_row = head;
|
||||||
|
}
|
||||||
|
Container::TableRow { head }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if enter {
|
if enter {
|
||||||
|
|
62
src/tree.rs
62
src/tree.rs
|
@ -157,7 +157,18 @@ pub struct Builder<C, A> {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Clone, A: Clone> Builder<C, A> {
|
impl<'a, C, A> From<&'a mut NodeKind<C, A>> for Element<'a, C, A> {
|
||||||
|
fn from(kind: &'a mut NodeKind<C, A>) -> Self {
|
||||||
|
match kind {
|
||||||
|
NodeKind::Root => unreachable!(),
|
||||||
|
NodeKind::Container(c, ..) => Element::Container(c),
|
||||||
|
NodeKind::Atom(a) => Element::Atom(a),
|
||||||
|
NodeKind::Inline => Element::Inline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: std::fmt::Debug, A: std::fmt::Debug> Builder<C, A> {
|
||||||
pub(super) fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
Builder {
|
Builder {
|
||||||
nodes: vec![Node {
|
nodes: vec![Node {
|
||||||
|
@ -206,6 +217,19 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Exit and discard all the contents of the current container.
|
||||||
|
pub(super) fn exit_discard(&mut self) {
|
||||||
|
self.exit();
|
||||||
|
let exited = self.branch.pop().unwrap();
|
||||||
|
self.nodes.drain(exited.index()..);
|
||||||
|
let (ni, has_parent) = self.relink(exited, None);
|
||||||
|
if has_parent {
|
||||||
|
self.head = Some(ni);
|
||||||
|
} else {
|
||||||
|
self.branch.push(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn depth(&self) -> usize {
|
pub(super) fn depth(&self) -> usize {
|
||||||
self.depth
|
self.depth
|
||||||
}
|
}
|
||||||
|
@ -219,6 +243,23 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve all children nodes for the specified node. Order is in the order they were added.
|
||||||
|
pub(super) fn children(
|
||||||
|
&mut self,
|
||||||
|
node: NodeIndex,
|
||||||
|
) -> impl Iterator<Item = (Element<C, A>, Span)> {
|
||||||
|
assert!(matches!(
|
||||||
|
self.nodes[node.index()].kind,
|
||||||
|
NodeKind::Container(..)
|
||||||
|
));
|
||||||
|
let end = self.nodes[node.index()]
|
||||||
|
.next
|
||||||
|
.map_or(self.nodes.len(), NodeIndex::index);
|
||||||
|
self.nodes[node.index()..end]
|
||||||
|
.iter_mut()
|
||||||
|
.map(|n| (Element::from(&mut n.kind), n.span))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn finish(self) -> Tree<C, A> {
|
pub(super) fn finish(self) -> Tree<C, A> {
|
||||||
assert_eq!(self.depth, 0);
|
assert_eq!(self.depth, 0);
|
||||||
let head = self.nodes[NodeIndex::root().index()].next;
|
let head = self.nodes[NodeIndex::root().index()].next;
|
||||||
|
@ -257,6 +298,25 @@ impl<C: Clone, A: Clone> Builder<C, A> {
|
||||||
self.head = Some(ni);
|
self.head = Some(ni);
|
||||||
ni
|
ni
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove the link from the node that points to the specified node. Return the pointer node
|
||||||
|
/// and whether it is a container or not.
|
||||||
|
fn relink(&mut self, prev: NodeIndex, next: Option<NodeIndex>) -> (NodeIndex, bool) {
|
||||||
|
for (i, n) in self.nodes.iter_mut().enumerate().rev() {
|
||||||
|
let ni = NodeIndex::new(i);
|
||||||
|
if n.next == Some(prev) {
|
||||||
|
n.next = next;
|
||||||
|
return (ni, false);
|
||||||
|
} else if let NodeKind::Container(kind, child) = &mut n.kind {
|
||||||
|
if *child == Some(prev) {
|
||||||
|
dbg!(kind, next);
|
||||||
|
*child = next;
|
||||||
|
return (ni, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: std::fmt::Debug + Clone + 'static, A: std::fmt::Debug + Clone + 'static> std::fmt::Debug
|
impl<C: std::fmt::Debug + Clone + 'static, A: std::fmt::Debug + Clone + 'static> std::fmt::Debug
|
||||||
|
|
Loading…
Reference in a new issue