diff --git a/src/block.rs b/src/block.rs index 241dcb3..f1c46cd 100644 --- a/src/block.rs +++ b/src/block.rs @@ -65,6 +65,9 @@ pub enum Leaf { /// Has zero or one inline for the cell contents. TableCell(Alignment), + /// Span is '^' character. + Caption, + /// Span is the link tag. /// Inlines are lines of the URL. LinkDefinition, @@ -268,7 +271,25 @@ impl<'s> TreeParser<'s> { self.alignments.clear(); self.tree.enter(Node::Container(Table), span); 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 .tree .enter(Node::Container(TableRow { head: false }), row.with_len(1)); @@ -468,6 +489,7 @@ struct BlockParser { kind: Block, span: Span, fence: Option<(char, usize)>, + caption: bool, } impl BlockParser { @@ -595,6 +617,7 @@ impl BlockParser { kind, span, fence, + caption: false, } } @@ -638,14 +661,26 @@ impl BlockParser { !((&mut c).take(fence_length).all(|c| c == fence) && c.next().map_or(true, char::is_whitespace)) } - Block::Container(List { .. } | DescriptionList | TableRow { .. }) - | Block::Leaf(TableCell(..)) => { - panic!() - } + Block::Container(Table) if self.caption => !line.trim().is_empty(), Block::Container(Table) => { let line = line.trim(); let l = line.len(); - line.as_bytes()[l - 1] == b'|' && line.as_bytes()[l - 2] != b'\\' + 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'\\' + } + } + } + } + Block::Container(List { .. } | DescriptionList | TableRow { .. }) + | Block::Leaf(TableCell(..) | Caption) => { + panic!() } } } @@ -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] fn parse_table_sep_row_only() { test_parse!( diff --git a/src/html.rs b/src/html.rs index 7f94328..2e8c407 100644 --- a/src/html.rs +++ b/src/html.rs @@ -157,6 +157,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Container::Heading { level } => write!(self.out, " self.out.write_str(" self.out.write_str(" self.out.write_str(" self.out.write_str(" self.out.write_str(" self.out.write_str(">, W: std::fmt::Write> Writer<'s, I, W> { Container::Heading { level } => write!(self.out, "", level)?, Container::TableCell { head: false, .. } => self.out.write_str("")?, Container::TableCell { head: true, .. } => self.out.write_str("")?, + Container::Caption => self.out.write_str("")?, Container::DescriptionTerm => self.out.write_str("")?, Container::CodeBlock { .. } => self.out.write_str("")?, Container::Span => self.out.write_str("")?, diff --git a/src/lib.rs b/src/lib.rs index 469f9c9..b4698f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,8 @@ pub enum Container<'s> { Heading { level: usize }, /// A cell element of row within a table. TableCell { alignment: Alignment, head: bool }, + /// A caption within a table. + Caption, /// A term within a description list. DescriptionTerm, /// A block with raw markup for a specific output format. @@ -111,6 +113,7 @@ impl<'s> Container<'s> { | Self::Paragraph | Self::Heading { .. } | Self::TableCell { .. } + | Self::Caption | Self::DescriptionTerm | Self::RawBlock { .. } | Self::CodeBlock { .. } => true, @@ -148,6 +151,7 @@ impl<'s> Container<'s> { Self::Paragraph | Self::Heading { .. } | Self::TableCell { .. } + | Self::Caption | Self::DescriptionTerm | Self::RawBlock { .. } | Self::CodeBlock { .. } @@ -541,6 +545,7 @@ impl<'s> Parser<'s> { alignment, head: self.table_head_row, }, + block::Leaf::Caption => Container::Caption, block::Leaf::LinkDefinition => unreachable!(), } }