use super::stack_node::StackNode;
use super::ConversionError;
use crate::ir::events::Event;
use crate::ir::nodes::*;
fn finalize_container<F>(
stack: &mut Vec<StackNode>,
event_name: &str,
parent_label: &str,
validate: F,
) -> Result<(), ConversionError>
where
F: FnOnce(StackNode) -> Result<StackNode, ConversionError>,
{
let node = stack
.pop()
.ok_or_else(|| ConversionError::UnexpectedEnd(format!("{event_name} with empty stack")))?;
let node = validate(node)?;
let doc_node = node.into_doc_node();
let parent = stack
.last_mut()
.ok_or_else(|| ConversionError::UnexpectedEnd(format!("No parent for {parent_label}")))?;
parent.add_child(doc_node)?;
Ok(())
}
fn auto_close_headings_at_or_deeper(
stack: &mut Vec<StackNode>,
new_level: usize,
) -> Result<(), ConversionError> {
let mut headings_to_close = Vec::new();
for (i, node) in stack.iter().enumerate().rev() {
if let StackNode::Heading { level, .. } = node {
if *level >= new_level {
headings_to_close.push(i);
} else {
break;
}
} else {
break;
}
}
for _ in 0..headings_to_close.len() {
finalize_container(stack, "auto-close heading", "heading", |node| match node {
StackNode::Heading { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Heading".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Ok(())
}
fn auto_close_all_headings(stack: &mut Vec<StackNode>) -> Result<(), ConversionError> {
let mut heading_count = 0;
for node in stack.iter().rev() {
if matches!(node, StackNode::Heading { .. }) {
heading_count += 1;
} else {
break;
}
}
for _ in 0..heading_count {
finalize_container(
stack,
"auto-close heading at end",
"heading",
|node| match node {
StackNode::Heading { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Heading".to_string(),
found: other.type_name().to_string(),
}),
},
)?;
}
Ok(())
}
pub fn events_to_tree(events: &[Event]) -> Result<Document, ConversionError> {
if events.is_empty() {
return Ok(Document {
title: None,
subtitle: None,
children: vec![],
document_annotations: vec![],
});
}
let mut stack: Vec<StackNode> = Vec::new();
let mut event_iter = events.iter().peekable();
match event_iter.next() {
Some(Event::StartDocument) => {
stack.push(StackNode::Document(Document {
title: None,
subtitle: None,
children: vec![],
document_annotations: vec![],
}));
}
Some(other) => {
return Err(ConversionError::MismatchedEvents {
expected: "StartDocument".to_string(),
found: format!("{other:?}"),
});
}
None => {
return Ok(Document {
title: None,
subtitle: None,
children: vec![],
document_annotations: vec![],
})
}
}
while let Some(event) = event_iter.next() {
match event {
Event::StartDocument => {
return Err(ConversionError::MismatchedEvents {
expected: "content or EndDocument".to_string(),
found: "StartDocument".to_string(),
});
}
Event::EndDocument => {
auto_close_all_headings(&mut stack)?;
if stack.len() != 1 {
return Err(ConversionError::UnclosedContainers(stack.len() - 1));
}
let doc_node = stack.pop().unwrap();
if let StackNode::Document(doc) = doc_node {
if event_iter.peek().is_some() {
return Err(ConversionError::ExtraEvents);
}
return Ok(doc);
} else {
return Err(ConversionError::MismatchedEvents {
expected: "Document".to_string(),
found: doc_node.type_name().to_string(),
});
}
}
Event::StartHeading(level) => {
auto_close_headings_at_or_deeper(&mut stack, *level)?;
let node = StackNode::Heading {
level: *level,
content: vec![],
children: vec![],
};
stack.push(node);
}
Event::EndHeading(level) => {
finalize_container(&mut stack, "EndHeading", "heading", |node| match node {
StackNode::Heading {
level: node_level, ..
} if node_level == *level => Ok(node),
StackNode::Heading {
level: node_level, ..
} => Err(ConversionError::MismatchedEvents {
expected: format!("EndHeading({node_level})"),
found: format!("EndHeading({level})"),
}),
other => Err(ConversionError::MismatchedEvents {
expected: "Heading".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartContent => {
}
Event::EndContent => {
}
Event::StartParagraph => {
stack.push(StackNode::Paragraph { content: vec![] });
}
Event::EndParagraph => {
finalize_container(&mut stack, "EndParagraph", "paragraph", |node| match node {
StackNode::Paragraph { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Paragraph".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartList {
ordered,
style,
form,
} => {
stack.push(StackNode::List {
items: vec![],
ordered: *ordered,
style: *style,
form: *form,
});
}
Event::EndList => {
finalize_container(&mut stack, "EndList", "list", |node| match node {
StackNode::List { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "List".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartListItem => {
stack.push(StackNode::ListItem {
content: vec![],
children: vec![],
});
}
Event::EndListItem => {
finalize_container(&mut stack, "EndListItem", "list item", |node| match node {
StackNode::ListItem { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "ListItem".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartDefinition => {
stack.push(StackNode::Definition {
term: vec![],
description: vec![],
in_term: false,
});
}
Event::EndDefinition => {
finalize_container(
&mut stack,
"EndDefinition",
"definition",
|node| match node {
StackNode::Definition { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Definition".to_string(),
found: other.type_name().to_string(),
}),
},
)?;
}
Event::StartDefinitionTerm => {
if let Some(StackNode::Definition { in_term, .. }) = stack.last_mut() {
*in_term = true;
} else {
return Err(ConversionError::MismatchedEvents {
expected: "Definition on stack".to_string(),
found: "StartDefinitionTerm".to_string(),
});
}
}
Event::EndDefinitionTerm => {
if let Some(StackNode::Definition { in_term, .. }) = stack.last_mut() {
*in_term = false;
} else {
return Err(ConversionError::MismatchedEvents {
expected: "Definition on stack".to_string(),
found: "EndDefinitionTerm".to_string(),
});
}
}
Event::StartDefinitionDescription => {
}
Event::EndDefinitionDescription => {
}
Event::StartVerbatim {
language,
subject,
subject_href,
parameters,
} => {
stack.push(StackNode::Verbatim {
subject: subject.clone(),
subject_href: subject_href.clone(),
language: language.clone(),
content: String::new(),
parameters: parameters.clone(),
});
}
Event::EndVerbatim => {
finalize_container(&mut stack, "EndVerbatim", "verbatim", |node| match node {
StackNode::Verbatim { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Verbatim".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartAnnotation {
label,
parameters,
form,
} => {
stack.push(StackNode::Annotation {
label: label.clone(),
parameters: parameters.clone(),
content: vec![],
form: *form,
});
}
Event::EndAnnotation { label } => {
finalize_container(
&mut stack,
"EndAnnotation",
"annotation",
|node| match node {
StackNode::Annotation {
label: ref node_label,
..
} if node_label == label || label.is_empty() => Ok(node),
StackNode::Annotation {
label: ref node_label,
..
} => Err(ConversionError::MismatchedEvents {
expected: format!("EndAnnotation({node_label})"),
found: format!("EndAnnotation({label})"),
}),
other => Err(ConversionError::MismatchedEvents {
expected: "Annotation".to_string(),
found: other.type_name().to_string(),
}),
},
)?;
}
Event::StartTable { caption, fullwidth } => {
stack.push(StackNode::Table {
rows: vec![],
header: vec![],
caption: caption.clone(),
footnotes: vec![],
fullwidth: *fullwidth,
});
}
Event::EndTable => {
finalize_container(&mut stack, "EndTable", "table", |node| match node {
StackNode::Table { .. } => Ok(node),
other => Err(ConversionError::MismatchedEvents {
expected: "Table".to_string(),
found: other.type_name().to_string(),
}),
})?;
}
Event::StartTableRow { header } => {
stack.push(StackNode::TableRow {
cells: vec![],
header: *header,
});
}
Event::EndTableRow => {
let node = stack.pop().ok_or_else(|| {
ConversionError::UnexpectedEnd("EndTableRow with empty stack".to_string())
})?;
match node {
StackNode::TableRow { cells, header } => {
let row = TableRow { cells };
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd("No parent for table row".to_string())
})?;
match parent {
StackNode::Table {
rows,
header: table_header,
..
} => {
if header {
table_header.push(row);
} else {
rows.push(row);
}
Ok(())
}
_ => Err(ConversionError::MismatchedEvents {
expected: "Table".to_string(),
found: parent.type_name().to_string(),
}),
}?;
}
other => {
return Err(ConversionError::MismatchedEvents {
expected: "TableRow".to_string(),
found: other.type_name().to_string(),
})
}
}
}
Event::StartTableCell {
header,
align,
colspan,
rowspan,
} => {
stack.push(StackNode::TableCell {
content: vec![],
header: *header,
align: *align,
colspan: *colspan,
rowspan: *rowspan,
});
}
Event::EndTableCell => {
let node = stack.pop().ok_or_else(|| {
ConversionError::UnexpectedEnd("EndTableCell with empty stack".to_string())
})?;
match node {
StackNode::TableCell {
content,
header,
align,
colspan,
rowspan,
} => {
let cell = TableCell {
content,
header,
align,
colspan,
rowspan,
};
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd("No parent for table cell".to_string())
})?;
match parent {
StackNode::TableRow { cells, .. } => {
cells.push(cell);
Ok(())
}
_ => Err(ConversionError::MismatchedEvents {
expected: "TableRow".to_string(),
found: parent.type_name().to_string(),
}),
}?;
}
other => {
return Err(ConversionError::MismatchedEvents {
expected: "TableCell".to_string(),
found: other.type_name().to_string(),
})
}
}
}
Event::StartTableFootnotes => {
stack.push(StackNode::TableFootnotes { content: vec![] });
}
Event::EndTableFootnotes => {
let node = stack.pop().ok_or_else(|| {
ConversionError::UnexpectedEnd("EndTableFootnotes with empty stack".to_string())
})?;
match node {
StackNode::TableFootnotes { content } => {
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd(
"No parent for table footnotes".to_string(),
)
})?;
match parent {
StackNode::Table { footnotes, .. } => {
*footnotes = content;
Ok(())
}
_ => Err(ConversionError::MismatchedEvents {
expected: "Table".to_string(),
found: parent.type_name().to_string(),
}),
}?;
}
other => {
return Err(ConversionError::MismatchedEvents {
expected: "TableFootnotes".to_string(),
found: other.type_name().to_string(),
})
}
}
}
Event::Image(image) => {
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd("Image event with empty stack".to_string())
})?;
parent.add_child(DocNode::Image(image.clone()))?;
}
Event::Video(video) => {
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd("Video event with empty stack".to_string())
})?;
parent.add_child(DocNode::Video(video.clone()))?;
}
Event::Audio(audio) => {
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedEnd("Audio event with empty stack".to_string())
})?;
parent.add_child(DocNode::Audio(audio.clone()))?;
}
Event::Inline(inline) => {
let parent = stack.last_mut().ok_or_else(|| {
ConversionError::UnexpectedInline("Inline content with no parent".to_string())
})?;
parent.add_inline(inline.clone())?;
}
}
}
Err(ConversionError::UnclosedContainers(stack.len()))
}