use crate::ast::tokens::token::{Block, Inline};
pub trait AstVisitor {
fn visit_block(&mut self, _block: &mut Block) -> VisitControl {
VisitControl::default()
}
fn visit_inline(&mut self, _inline: &mut Inline) -> InlineVisitControl {
InlineVisitControl::default()
}
}
#[derive(Debug, Default)]
pub struct VisitControl {
pub insert_before: Vec<Block>,
pub insert_after: Vec<Block>,
pub replace_with: Option<Vec<Block>>,
pub remove: bool,
pub recurse: bool,
}
impl VisitControl {
pub fn keep_and_recurse() -> Self {
VisitControl {
recurse: true,
..Default::default()
}
}
pub fn remove() -> Self {
VisitControl {
remove: true,
..Default::default()
}
}
pub fn replace_with(nodes: Vec<Block>) -> Self {
VisitControl {
replace_with: Some(nodes),
..Default::default()
}
}
}
#[derive(Debug, Default)]
pub struct InlineVisitControl {
pub insert_before: Vec<Inline>,
pub insert_after: Vec<Inline>,
pub replace_with: Option<Vec<Inline>>,
pub remove: bool,
pub recurse: bool,
}
impl InlineVisitControl {
pub fn keep_and_recurse() -> Self {
InlineVisitControl {
recurse: true,
..Default::default()
}
}
pub fn remove() -> Self {
InlineVisitControl {
remove: true,
..Default::default()
}
}
pub fn replace_with(nodes: Vec<Inline>) -> Self {
InlineVisitControl {
replace_with: Some(nodes),
..Default::default()
}
}
}
pub fn visit_document_mut<V: AstVisitor + ?Sized>(doc: &mut super::Document, visitor: &mut V) {
visit_blocks_vec(&mut doc.children, visitor)
}
fn visit_blocks_vec<V: AstVisitor + ?Sized>(children: &mut Vec<Block>, visitor: &mut V) {
let mut i = 0usize;
while i < children.len() {
let mut node = children.remove(i);
let ctrl = visitor.visit_block(&mut node);
let VisitControl {
insert_before,
insert_after,
replace_with,
remove,
recurse,
} = ctrl;
if !insert_before.is_empty() {
let before_len = insert_before.len();
children.splice(i..i, insert_before);
i += before_len;
}
if remove {
if !insert_after.is_empty() {
let after_len = insert_after.len();
children.splice(i..i, insert_after);
i += after_len;
}
continue;
}
if let Some(reps) = replace_with {
let replacement_len = reps.len();
children.splice(i..i, reps);
i += replacement_len;
continue;
}
if recurse {
match &mut node {
Block::Heading {
children: inline_children,
..
}
| Block::Paragraph {
children: inline_children,
..
} => {
visit_inlines_vec(inline_children, visitor);
}
Block::BlockQuote {
children: block_children,
..
} => {
visit_blocks_vec(block_children, visitor);
}
Block::List { items, .. } => {
for item in items.iter_mut() {
visit_blocks_vec(&mut item.children, visitor);
}
}
Block::Table { table, .. } => {
visit_table(table, visitor);
}
_ => {}
}
}
children.insert(i, node);
i += 1;
if !insert_after.is_empty() {
let after_len = insert_after.len();
children.splice(i..i, insert_after);
i += after_len;
}
}
}
fn visit_table<V: AstVisitor + ?Sized>(
table: &mut crate::ast::tokens::token::Table,
visitor: &mut V,
) {
for cell in table.header.cells.iter_mut() {
visit_inlines_vec(&mut cell.children, visitor);
}
for row in table.rows.iter_mut() {
for cell in row.cells.iter_mut() {
visit_inlines_vec(&mut cell.children, visitor);
}
}
}
fn visit_inlines_vec<V: AstVisitor + ?Sized>(inlines: &mut Vec<Inline>, visitor: &mut V) {
let mut i = 0usize;
while i < inlines.len() {
let mut node = inlines.remove(i);
let ctrl = visitor.visit_inline(&mut node);
let InlineVisitControl {
insert_before,
insert_after,
replace_with,
remove,
recurse,
} = ctrl;
if !insert_before.is_empty() {
let before_len = insert_before.len();
inlines.splice(i..i, insert_before);
i += before_len;
}
if remove {
if !insert_after.is_empty() {
let after_len = insert_after.len();
inlines.splice(i..i, insert_after);
i += after_len;
}
continue;
}
#[allow(unused_mut)]
if let Some(reps) = replace_with {
inlines.splice(i..i, reps);
continue;
}
if recurse {
match &mut node {
Inline::Emphasis { children, .. } | Inline::Strikethrough { children, .. } => {
visit_inlines_vec(children, visitor);
}
Inline::Link { text, .. } | Inline::LinkReference { text, .. } => {
visit_inlines_vec(text, visitor);
}
_ => {}
}
}
inlines.insert(i, node);
i += 1;
if !insert_after.is_empty() {
let after_len = insert_after.len();
inlines.splice(i..i, insert_after);
i += after_len;
}
}
}