Scaffold table token
This commit is contained in:
parent
4187299e04
commit
b890eb93f1
6 changed files with 217 additions and 4 deletions
|
|
@ -8,6 +8,7 @@ pub mod inline;
|
|||
pub mod anchor;
|
||||
pub mod list;
|
||||
pub mod quote;
|
||||
pub mod table;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Context {
|
||||
|
|
@ -22,6 +23,7 @@ pub enum Block {
|
|||
List,
|
||||
PreFormat,
|
||||
Quote,
|
||||
Table,
|
||||
Verse,
|
||||
#[default]
|
||||
None,
|
||||
|
|
@ -54,6 +56,9 @@ pub fn close(state: &State, tokens: &mut Vec<Token>) {
|
|||
Block::Quote => {
|
||||
panic!("End of input with open quote")
|
||||
},
|
||||
Block::Table => {
|
||||
panic!("End of input with open table")
|
||||
},
|
||||
Block::Verse => {
|
||||
tokens.push(Token::Verse(Verse::new(false)));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
Block, Lexeme, State, Token,
|
||||
token::{
|
||||
Header, List, LineBreak, Literal, Paragraph, PreFormat, Quote,
|
||||
Verse,
|
||||
Table, Verse,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -59,6 +59,11 @@ pub fn parse(
|
|||
iterator.next();
|
||||
iterator.next();
|
||||
return true;
|
||||
} else if Table::probe(lexeme) {
|
||||
log!(VERBOSE, "Block Context: None -> Table on {lexeme}");
|
||||
state.context.block = Block::Table;
|
||||
iterator.next();
|
||||
return true;
|
||||
} else if Paragraph::probe(lexeme) {
|
||||
log!(VERBOSE, "Block Context: None -> Paragraph on {lexeme}");
|
||||
state.context.block = Block::Paragraph;
|
||||
|
|
@ -95,6 +100,9 @@ pub fn parse(
|
|||
Block::Quote => {
|
||||
return super::quote::parse(lexeme, state, tokens, iterator, graph);
|
||||
},
|
||||
Block::Table => {
|
||||
return super::table::parse(lexeme, state, tokens, iterator, graph);
|
||||
},
|
||||
Block::Verse => {
|
||||
if Verse::probe_end(lexeme) {
|
||||
tokens.push(Token::Verse(Verse::new(false)));
|
||||
|
|
|
|||
114
src/syntax/content/parser/context/table.rs
Normal file
114
src/syntax/content/parser/context/table.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use std::{iter::Peekable, slice::Iter};
|
||||
|
||||
use crate::{
|
||||
graph::Graph,
|
||||
prelude::*,
|
||||
syntax::content::parser::{
|
||||
Lexeme, State, Token, context::Block, format, state, token::Table,
|
||||
},
|
||||
};
|
||||
|
||||
/// Handles open table contexts until a table is fully parsed.
|
||||
///
|
||||
/// A return of `true` will trigger a continue in the outer parser,
|
||||
/// skipping any further parsing of the current lexeme.
|
||||
///
|
||||
/// # Panics
|
||||
/// This parser can handle only the Table context, and will panic if passed an
|
||||
/// unrelated context since it has no knowledge on how to handle them.
|
||||
pub fn parse(
|
||||
lexeme: &Lexeme,
|
||||
state: &mut State,
|
||||
tokens: &mut Vec<Token>,
|
||||
iterator: &mut Peekable<Iter<'_, Lexeme>>,
|
||||
graph: &Graph,
|
||||
) -> bool {
|
||||
let buffer = &mut state.buffers.table;
|
||||
let candidate = &mut buffer.candidate;
|
||||
|
||||
let mut parse_text = |text: &str| {
|
||||
let (parsed_text, text_tokens) = format(text, graph);
|
||||
state.format_tokens.extend_from_slice(&text_tokens);
|
||||
parsed_text
|
||||
};
|
||||
|
||||
#[allow(clippy::wildcard_enum_match_arm)]
|
||||
match state.context.block {
|
||||
Block::Table => {
|
||||
if Table::probe_end(lexeme) {
|
||||
log!(VERBOSE, "Probed end of table on {lexeme}");
|
||||
|
||||
if buffer.in_header {
|
||||
log!(VERBOSE, "Adding unterminated header: {lexeme}");
|
||||
candidate.add_header(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else if buffer.in_cell {
|
||||
log!(VERBOSE, "Adding unterminated cell: {lexeme}");
|
||||
candidate.add_cell(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else {
|
||||
log!(VERBOSE, "Adding undelimited cell: {lexeme}");
|
||||
candidate.add_cell(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
tokens.push(Token::Table(candidate.clone()));
|
||||
log!(VERBOSE, "Block Context: Table -> None on {lexeme}");
|
||||
state.context.block = Block::None;
|
||||
*buffer = state::TableBuffer::default();
|
||||
iterator.next();
|
||||
} else if lexeme.match_char('\n') {
|
||||
log!(VERBOSE, "Adding row: found newline on {lexeme}");
|
||||
if !buffer.cell.is_empty() {
|
||||
|
||||
if buffer.in_header {
|
||||
log!(VERBOSE, "Adding unterminated header: {lexeme}");
|
||||
candidate.add_header(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else if buffer.in_cell {
|
||||
log!(VERBOSE, "Adding unterminated cell: {lexeme}");
|
||||
candidate.add_cell(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else {
|
||||
log!(VERBOSE, "Adding undelimited cell: {lexeme}");
|
||||
candidate.add_cell(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
}
|
||||
candidate.add_row(vec![]);
|
||||
} else if lexeme.match_char_triple(' ', '!', ' ') {
|
||||
log!(VERBOSE, "Adding header: found spaced ! on {lexeme}");
|
||||
candidate.add_header(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else if lexeme.match_char_triple(' ', '|', ' ') {
|
||||
log!(VERBOSE, "Adding cell: found spaced | on {lexeme}");
|
||||
candidate.add_cell(&parse_text(&buffer.cell));
|
||||
buffer.cell.clear();
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else {
|
||||
log!(VERBOSE, "Extending cell text on {lexeme}");
|
||||
buffer.cell.push_str(lexeme.text().as_str());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("Table context parser called to handle non-table context")
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||
use crate::syntax::content::parser::{
|
||||
Token,
|
||||
context::Context,
|
||||
token::{Anchor, Item, List, Quote},
|
||||
token::{Anchor, Item, List, Quote, Table},
|
||||
};
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
|
|
@ -28,6 +28,7 @@ pub struct Buffers {
|
|||
pub anchor: AnchorBuffer,
|
||||
pub list: ListBuffer,
|
||||
pub quote: QuoteBuffer,
|
||||
pub table: TableBuffer,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
|
@ -50,6 +51,14 @@ pub struct QuoteBuffer {
|
|||
pub in_citation: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct TableBuffer {
|
||||
pub candidate: Table,
|
||||
pub cell: String,
|
||||
pub in_cell: bool,
|
||||
pub in_header: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AnchorBuffer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let display_text = if self.text.is_empty() {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@ pub mod paragraph;
|
|||
pub mod preformat;
|
||||
pub mod quote;
|
||||
pub mod strike;
|
||||
pub mod table;
|
||||
pub mod underline;
|
||||
pub mod verse;
|
||||
|
||||
pub use {
|
||||
anchor::Anchor, bold::Bold, checkbox::CheckBox, code::Code, header::Header,
|
||||
item::Item, linebreak::LineBreak, list::List, literal::Literal,
|
||||
oblique::Oblique, paragraph::Paragraph, preformat::PreFormat,
|
||||
strike::Strike, underline::Underline, quote::Quote, verse::Verse,
|
||||
oblique::Oblique, paragraph::Paragraph, preformat::PreFormat, quote::Quote,
|
||||
strike::Strike, table::Table, underline::Underline, verse::Verse,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
|
|
@ -40,6 +41,7 @@ pub enum Token {
|
|||
Paragraph(Paragraph),
|
||||
PreFormat(PreFormat),
|
||||
Quote(Quote),
|
||||
Table(Table),
|
||||
Underline(Underline),
|
||||
Verse(Verse),
|
||||
}
|
||||
|
|
@ -61,6 +63,7 @@ impl Token {
|
|||
Token::Paragraph(d) => d.render(),
|
||||
Token::PreFormat(d) => d.render(),
|
||||
Token::Quote(d) => d.render(),
|
||||
Token::Table(d) => d.render(),
|
||||
Token::Underline(d) => d.render(),
|
||||
Token::Verse(d) => d.render(),
|
||||
}
|
||||
|
|
@ -82,6 +85,7 @@ impl Token {
|
|||
Token::Paragraph(d) => d.flatten(),
|
||||
Token::PreFormat(d) => d.flatten(),
|
||||
Token::Quote(d) => d.flatten(),
|
||||
Token::Table(d) => d.flatten(),
|
||||
Token::Underline(d) => d.flatten(),
|
||||
Token::Verse(d) => d.flatten(),
|
||||
}
|
||||
|
|
@ -105,6 +109,7 @@ impl std::fmt::Display for Token {
|
|||
Token::Paragraph(d) => format!("{d}"),
|
||||
Token::PreFormat(d) => format!("{d}"),
|
||||
Token::Quote(d) => format!("{d}"),
|
||||
Token::Table(d) => format!("{d}"),
|
||||
Token::Underline(d) => format!("{d}"),
|
||||
Token::Verse(d) => format!("{d}"),
|
||||
};
|
||||
|
|
|
|||
72
src/syntax/content/parser/token/table.rs
Normal file
72
src/syntax/content/parser/token/table.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use crate::syntax::content::{Parseable, parser::Lexeme};
|
||||
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Table {
|
||||
pub headers: Vec<String>,
|
||||
pub contents: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Table {
|
||||
pub fn probe_end(lexeme: &Lexeme) -> bool {
|
||||
lexeme.match_char_triple('\n', '%', '\n') || lexeme.last()
|
||||
}
|
||||
|
||||
pub fn add_header(&mut self, header: &str) {
|
||||
self.headers.push(header.trim().to_string());
|
||||
}
|
||||
|
||||
pub fn add_row(&mut self, row: Vec<String>) {
|
||||
self.contents.push(row);
|
||||
}
|
||||
|
||||
pub fn add_cell(&mut self, content: &str) {
|
||||
if let Some(last) = self.contents.last_mut() {
|
||||
last.push(content.trim().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parseable for Table {
|
||||
fn probe(lexeme: &Lexeme) -> bool {
|
||||
lexeme.match_char_triple('\n', '%', '\n')
|
||||
}
|
||||
|
||||
fn lex(_lexeme: &Lexeme) -> Table {
|
||||
Table::default()
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let mut xml = String::from("\n<table>\n");
|
||||
|
||||
if !self.headers.is_empty() {
|
||||
xml.push_str("<tr>\n");
|
||||
for header in &self.headers {
|
||||
xml.push_str(format!("<th>{header}</th>\n").as_str());
|
||||
}
|
||||
xml.push_str("\n</tr>\n");
|
||||
}
|
||||
|
||||
for row in &self.contents {
|
||||
if !row.is_empty() {
|
||||
xml.push_str("<tr>\n");
|
||||
for cell in row {
|
||||
xml.push_str(format!("<td>{cell}</td>\n").as_str());
|
||||
}
|
||||
xml.push_str("\n</tr>\n");
|
||||
}
|
||||
}
|
||||
|
||||
xml.push_str("\n</table>\n");
|
||||
xml
|
||||
}
|
||||
|
||||
fn flatten(&self) -> String {
|
||||
String::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Table {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Table")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue