block: parse captions
This commit is contained in:
parent
99f10fda4c
commit
8339befe2f
3 changed files with 111 additions and 6 deletions
108
src/block.rs
108
src/block.rs
|
@ -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!(
|
||||||
|
|
|
@ -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>")?,
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue