block: parse captions

This commit is contained in:
Noah Hellman 2023-01-26 20:16:20 +01:00
parent 99f10fda4c
commit 8339befe2f
3 changed files with 111 additions and 6 deletions

View file

@ -65,6 +65,9 @@ pub enum Leaf {
/// Has zero or one inline for the cell contents. /// Has zero or one inline for the cell contents.
TableCell(Alignment), TableCell(Alignment),
/// Span is '^' character.
Caption,
/// Span is the link tag. /// Span is the link tag.
/// Inlines are lines of the URL. /// Inlines are lines of the URL.
LinkDefinition, LinkDefinition,
@ -268,7 +271,25 @@ impl<'s> TreeParser<'s> {
self.alignments.clear(); self.alignments.clear();
self.tree.enter(Node::Container(Table), span); self.tree.enter(Node::Container(Table), span);
let mut last_row_node = None; let mut last_row_node = None;
for row in lines { let caption_line = lines
.iter()
.position(|sp| sp.of(self.src).trim_start().starts_with('^'))
.map_or(lines.len(), |caption_line| {
self.tree.enter(Node::Leaf(Caption), span);
lines[caption_line] =
lines[caption_line].trim_start(self.src).skip("^ ".len());
lines[lines.len() - 1] = lines[lines.len() - 1].trim_end(self.src);
for line in &lines[caption_line..] {
self.tree.inline(*line);
}
self.tree.exit(); // caption, will insert inlines later if any
caption_line
});
for row in &lines[..caption_line] {
let row = row.trim(self.src);
if row.is_empty() {
break;
}
let row_node = self let row_node = self
.tree .tree
.enter(Node::Container(TableRow { head: false }), row.with_len(1)); .enter(Node::Container(TableRow { head: false }), row.with_len(1));
@ -468,6 +489,7 @@ struct BlockParser {
kind: Block, kind: Block,
span: Span, span: Span,
fence: Option<(char, usize)>, fence: Option<(char, usize)>,
caption: bool,
} }
impl BlockParser { impl BlockParser {
@ -595,6 +617,7 @@ impl BlockParser {
kind, kind,
span, span,
fence, fence,
caption: false,
} }
} }
@ -638,17 +661,29 @@ 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 | TableRow { .. }) Block::Container(Table) if self.caption => !line.trim().is_empty(),
| Block::Leaf(TableCell(..)) => {
panic!()
}
Block::Container(Table) => { Block::Container(Table) => {
let line = line.trim(); let line = line.trim();
let l = line.len(); let l = line.len();
match l {
0 => true,
1..=2 => false,
_ => {
if line.starts_with("^ ") {
self.caption = true;
true
} else {
line.as_bytes()[l - 1] == b'|' && line.as_bytes()[l - 2] != b'\\' line.as_bytes()[l - 1] == b'|' && line.as_bytes()[l - 2] != b'\\'
} }
} }
} }
}
Block::Container(List { .. } | DescriptionList | TableRow { .. })
| Block::Leaf(TableCell(..) | Caption) => {
panic!()
}
}
}
fn maybe_ordered_list_item( fn maybe_ordered_list_item(
mut first: char, mut first: char,
@ -1470,6 +1505,69 @@ mod test {
); );
} }
#[test]
fn parse_table_caption() {
test_parse!(
"|a|\n^ caption",
(Enter(Container(Table)), ""),
(Enter(Leaf(Caption)), ""),
(Inline, "caption"),
(Exit(Leaf(Caption)), ""),
(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)), ""),
);
}
#[test]
fn parse_table_caption_multiline() {
test_parse!(
concat!(
"|a|\n", //
"\n", //
"^ caption\n", //
"continued\n", //
"\n", //
"para\n", //
),
(Enter(Container(Table)), ""),
(Enter(Leaf(Caption)), ""),
(Inline, "caption\n"),
(Inline, "continued"),
(Exit(Leaf(Caption)), ""),
(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)), ""),
(Atom(Blankline), "\n"),
(Enter(Leaf(Paragraph)), ""),
(Inline, "para"),
(Exit(Leaf(Paragraph)), ""),
);
}
#[test]
fn parse_table_caption_empty() {
test_parse!(
"|a|\n^ ",
(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, "^"),
(Exit(Leaf(Paragraph)), ""),
);
}
#[test] #[test]
fn parse_table_sep_row_only() { fn parse_table_sep_row_only() {
test_parse!( test_parse!(

View file

@ -157,6 +157,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
Container::Heading { level } => write!(self.out, "<h{}", level)?, Container::Heading { level } => write!(self.out, "<h{}", level)?,
Container::TableCell { head: false, .. } => self.out.write_str("<td")?, Container::TableCell { head: false, .. } => self.out.write_str("<td")?,
Container::TableCell { head: true, .. } => self.out.write_str("<th")?, Container::TableCell { head: true, .. } => self.out.write_str("<th")?,
Container::Caption => self.out.write_str("<caption")?,
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")?,
@ -338,6 +339,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<'s, I, W> {
Container::Heading { level } => write!(self.out, "</h{}>", level)?, Container::Heading { level } => write!(self.out, "</h{}>", level)?,
Container::TableCell { head: false, .. } => self.out.write_str("</td>")?, Container::TableCell { head: false, .. } => self.out.write_str("</td>")?,
Container::TableCell { head: true, .. } => self.out.write_str("</th>")?, Container::TableCell { head: true, .. } => self.out.write_str("</th>")?,
Container::Caption => self.out.write_str("</caption>")?,
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>")?,

View file

@ -56,6 +56,8 @@ pub enum Container<'s> {
Heading { level: usize }, Heading { level: usize },
/// A cell element of row within a table. /// A cell element of row within a table.
TableCell { alignment: Alignment, head: bool }, TableCell { alignment: Alignment, head: bool },
/// A caption within a table.
Caption,
/// 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.
@ -111,6 +113,7 @@ impl<'s> Container<'s> {
| Self::Paragraph | Self::Paragraph
| Self::Heading { .. } | Self::Heading { .. }
| Self::TableCell { .. } | Self::TableCell { .. }
| Self::Caption
| Self::DescriptionTerm | Self::DescriptionTerm
| Self::RawBlock { .. } | Self::RawBlock { .. }
| Self::CodeBlock { .. } => true, | Self::CodeBlock { .. } => true,
@ -148,6 +151,7 @@ impl<'s> Container<'s> {
Self::Paragraph Self::Paragraph
| Self::Heading { .. } | Self::Heading { .. }
| Self::TableCell { .. } | Self::TableCell { .. }
| Self::Caption
| Self::DescriptionTerm | Self::DescriptionTerm
| Self::RawBlock { .. } | Self::RawBlock { .. }
| Self::CodeBlock { .. } | Self::CodeBlock { .. }
@ -541,6 +545,7 @@ impl<'s> Parser<'s> {
alignment, alignment,
head: self.table_head_row, head: self.table_head_row,
}, },
block::Leaf::Caption => Container::Caption,
block::Leaf::LinkDefinition => unreachable!(), block::Leaf::LinkDefinition => unreachable!(),
} }
} }