use crate::model::inline::{prepend_checkbox, Inline, Inlines};
use crate::model::node::{Node, NodeIter, ReferenceType};
use crate::model::writer::Block;
pub struct Projector {
header_level: usize,
parent: String,
}
impl Projector {
pub fn project<'a>(iter: impl NodeIter<'a>, parent: &str) -> Vec<Block> {
Projector {
header_level: 0,
parent: parent.to_string(),
}
.project_node(iter)
}
fn with(&self, header_level: usize) -> Projector {
Projector {
header_level,
parent: self.parent.clone(),
}
}
fn resolve_inlines(&self, inlines: Inlines) -> Inlines {
inlines
.into_iter()
.map(|i| self.resolve_inline(i))
.collect()
}
fn resolve_inline(&self, inline: Inline) -> Inline {
match inline {
Inline::Reference(reference) => {
let url = match reference.reference_type {
ReferenceType::Regular => reference.key.to_rel_link_url(&self.parent),
ReferenceType::WikiLink | ReferenceType::WikiLinkPiped => reference
.display_url
.clone()
.unwrap_or_else(|| reference.key.to_library_url()),
};
let inlines = match reference.reference_type {
ReferenceType::WikiLink => vec![],
_ => vec![Inline::Str(reference.text)],
};
Inline::Link(
url,
String::default(),
reference.reference_type.to_link_type(),
inlines,
)
}
Inline::Emph(v) => Inline::Emph(self.resolve_inlines(v)),
Inline::Strong(v) => Inline::Strong(self.resolve_inlines(v)),
Inline::Strikeout(v) => Inline::Strikeout(self.resolve_inlines(v)),
Inline::Underline(v) => Inline::Underline(self.resolve_inlines(v)),
Inline::Superscript(v) => Inline::Superscript(self.resolve_inlines(v)),
Inline::Subscript(v) => Inline::Subscript(self.resolve_inlines(v)),
Inline::SmallCaps(v) => Inline::SmallCaps(self.resolve_inlines(v)),
Inline::Image(url, title, v) => Inline::Image(url, title, self.resolve_inlines(v)),
Inline::Link(url, title, lt, v) => {
Inline::Link(url, title, lt, self.resolve_inlines(v))
}
other => other,
}
}
fn project_node<'a>(&self, iter: impl NodeIter<'a>) -> Vec<Block> {
let mut blocks = vec![];
if iter.node().is_none() {
return blocks;
}
match iter.node().unwrap() {
Node::Document(_, frontmatter) => {
if let Some(mapping) = frontmatter {
blocks.push(Block::Frontmatter(mapping.clone()));
}
if let Some(child) = iter.child() {
blocks.extend(self.with(self.header_level).project_node(child));
}
}
Node::Section(_) => {
blocks.push(Block::Header(
self.header_level as u8 + 1,
self.resolve_inlines(iter.inlines()),
));
if let Some(child) = iter.child() {
blocks.extend(self.with(self.header_level + 1).project_node(child));
}
}
Node::Quote() => {
if let Some(child) = iter.child() {
blocks.push(Block::BlockQuote(self.with(0).project_node(child)));
}
}
Node::BulletList() => {
if let Some(child) = iter.child() {
blocks.push(Block::BulletList(self.with(0).project_list_item(child)));
}
}
Node::OrderedList() => {
if let Some(child) = iter.child() {
blocks.push(Block::OrderedList(self.with(0).project_list_item(child)));
}
}
Node::Leaf(_) => {
blocks.push(Block::Para(self.resolve_inlines(iter.inlines())));
}
Node::Item(checked, _) => {
let inlines = prepend_checkbox(checked, self.resolve_inlines(iter.inlines()));
blocks.push(Block::Para(inlines));
if let Some(child) = iter.child() {
blocks.extend(self.with(self.header_level).project_node(child));
}
}
Node::Raw(_, _) => {
blocks.push(Block::CodeBlock(
iter.lang(),
iter.content().unwrap_or_default(),
));
}
Node::HorizontalRule() => {
blocks.push(Block::HorizontalRule);
}
Node::Reference(reference) => {
let reference_type = reference.reference_type;
let inlines = match reference_type {
ReferenceType::Regular => self.resolve_inlines(iter.inlines()),
ReferenceType::WikiLink => vec![],
ReferenceType::WikiLinkPiped => {
vec![Inline::Str(iter.ref_text().unwrap_or_default())]
}
};
let url = match reference_type {
ReferenceType::Regular => reference.key.to_rel_link_url(&self.parent),
ReferenceType::WikiLink | ReferenceType::WikiLinkPiped => reference
.display_url
.clone()
.unwrap_or_else(|| reference.key.to_library_url()),
};
let link = Inline::Link(
url,
String::default(),
reference_type.to_link_type(),
inlines,
);
blocks.push(Block::Para(vec![link]));
}
Node::Table(_) => {
blocks.push(Block::Table(
iter.table_header()
.unwrap_or_default()
.into_iter()
.map(|cell| self.resolve_inlines(cell))
.collect(),
iter.table_alignment().unwrap_or_default(),
iter.table_rows()
.unwrap_or_default()
.into_iter()
.map(|row| {
row.into_iter()
.map(|cell| self.resolve_inlines(cell))
.collect()
})
.collect(),
));
}
}
if let Some(next) = iter.next() {
blocks.extend(self.with(self.header_level).project_node(next));
}
blocks
}
fn project_list_item<'a>(&self, iter: impl NodeIter<'a>) -> Vec<Vec<Block>> {
let mut items: Vec<Vec<Block>> = vec![];
if iter.node().is_none() {
return items;
}
let inlines = if iter.is_item() {
prepend_checkbox(iter.item_checked(), self.resolve_inlines(iter.inlines()))
} else {
self.resolve_inlines(iter.inlines())
};
if iter.child().map(|n| n.is_leaf()).unwrap_or(false) {
items.push(vec![Block::Para(inlines)]);
} else {
items.push(vec![Block::Plain(inlines)]);
}
if let Some(sub_list) = iter.child().map(|child| self.with(0).project_node(child)) {
sub_list
.iter()
.for_each(|item| items.last_mut().unwrap().push(item.clone()))
}
if let Some(blocks) = iter
.next()
.map(|next| self.with(self.header_level).project_list_item(next))
{
items.append(blocks.clone().as_mut())
}
items
}
}