use markdown::mdast;
use super::css::*;
use super::style::*;
#[derive(Debug, Clone)]
pub struct Node {
pub kind: NodeKind,
pub style: Style,
pub splittable: bool,
}
impl Node {
pub fn new(kind: NodeKind, style: Style, splittable: bool) -> Self {
Self {
kind,
style,
splittable,
}
}
pub fn text_content(&self) -> String {
self.kind.text_content()
}
}
#[derive(Debug, Clone)]
pub enum NodeKind {
Document { children: Vec<Node> },
Heading { level: u8, children: Vec<Node> },
Paragraph { children: Vec<Node> },
List {
ordered: bool,
start: Option<u32>,
children: Vec<Node>,
},
ListItem { children: Vec<Node> },
Image {
src: String,
alt: String,
title: Option<String>,
},
CodeBlock { code: String, lang: Option<String> },
Blockquote { children: Vec<Node> },
ThematicBreak,
Table {
children: Vec<Node>,
align: Vec<TextAlign>,
},
TableRow { children: Vec<Node> },
Text { text: String },
Strong { children: Vec<Node> },
Emphasis { children: Vec<Node> },
InlineCode { code: String },
Link {
url: String,
title: Option<String>,
children: Vec<Node>,
},
Delete { children: Vec<Node> },
}
impl NodeKind {
pub fn text_content(&self) -> String {
match self {
NodeKind::Text { text } => text.clone(),
NodeKind::Strong { children }
| NodeKind::Emphasis { children }
| NodeKind::Link { children, .. }
| NodeKind::Delete { children } => {
let mut s = String::new();
for child in children {
s.push_str(&child.text_content());
}
s
}
NodeKind::InlineCode { code } => code.clone(),
NodeKind::Heading { children, .. }
| NodeKind::Paragraph { children }
| NodeKind::ListItem { children }
| NodeKind::Blockquote { children }
| NodeKind::TableRow { children } => {
let mut s = String::new();
for child in children {
s.push_str(&child.text_content());
}
s
}
_ => String::new(),
}
}
}
pub fn build_ast(root: &mdast::Node, resolver: &StyleResolver) -> Node {
build_node(root, resolver, &[], &Style::default())
}
fn build_node(
node: &mdast::Node,
resolver: &StyleResolver,
ancestor_tags: &[String],
parent_style: &Style,
) -> Node {
match node {
mdast::Node::Root(root) => {
let tag = "body";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = root
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Document { children },
style,
false,
)
}
mdast::Node::Paragraph(_para) => {
let tag = "p";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children = build_inline_children(&_para.children, resolver, &new_ancestors, &style);
Node::new(
NodeKind::Paragraph { children },
style,
true,
)
}
mdast::Node::Heading(heading) => {
let tag = match heading.depth {
1 => "h1", 2 => "h2", 3 => "h3",
4 => "h4", 5 => "h5", 6 => "h6",
_ => "h1",
};
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children = build_inline_children(&heading.children, resolver, &new_ancestors, &style);
Node::new(
NodeKind::Heading {
level: heading.depth,
children,
},
style,
false,
)
}
mdast::Node::Code(code) => {
let tag = "pre";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
Node::new(
NodeKind::CodeBlock {
code: code.value.clone(),
lang: code.lang.clone(),
},
style,
false,
)
}
mdast::Node::Image(image) => {
let tag = "img";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
Node::new(
NodeKind::Image {
src: image.url.clone(),
alt: image.alt.clone(),
title: image.title.clone(),
},
style,
false,
)
}
mdast::Node::List(list) => {
let tag = if list.ordered { "ol" } else { "ul" };
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = list
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::List {
ordered: list.ordered,
start: list.start,
children,
},
style,
true,
)
}
mdast::Node::ListItem(item) => {
let tag = "li";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = item
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::ListItem { children },
style,
true,
)
}
mdast::Node::Blockquote(blockquote) => {
let tag = "blockquote";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = blockquote
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Blockquote { children },
style,
true,
)
}
mdast::Node::ThematicBreak(_) => {
let tag = "hr";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
Node::new(
NodeKind::ThematicBreak,
style,
false,
)
}
mdast::Node::Table(table) => {
let tag = "table";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let align: Vec<TextAlign> = table
.align
.iter()
.map(|a| match a {
mdast::AlignKind::Left => TextAlign::Left,
mdast::AlignKind::Right => TextAlign::Right,
mdast::AlignKind::Center => TextAlign::Center,
mdast::AlignKind::None => TextAlign::Left,
})
.collect();
let children: Vec<Node> = table
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Table { children, align },
style,
false,
)
}
mdast::Node::TableRow(row) => {
let tag = "tr";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = row
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::TableRow { children },
style,
false,
)
}
mdast::Node::TableCell(cell) => {
let tag = "td";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children = build_inline_children(&cell.children, resolver, &new_ancestors, &style);
Node::new(
NodeKind::Paragraph { children },
style,
true,
)
}
mdast::Node::Text(text) => {
let tag = "span";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
Node::new(
NodeKind::Text {
text: text.value.clone(),
},
style,
true,
)
}
mdast::Node::Strong(strong) => {
let tag = "strong";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = strong
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Strong { children },
style,
true,
)
}
mdast::Node::Emphasis(emph) => {
let tag = "em";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = emph
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Emphasis { children },
style,
true,
)
}
mdast::Node::InlineCode(code) => {
let tag = "code";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
Node::new(
NodeKind::InlineCode {
code: code.value.clone(),
},
style,
true,
)
}
mdast::Node::Link(link) => {
let tag = "a";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut style = style;
style.link_url = Some(link.url.clone());
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = link
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Link {
url: link.url.clone(),
title: link.title.clone(),
children,
},
style,
true,
)
}
mdast::Node::Delete(del) => {
let tag = "del";
let style = resolver.resolve_style(tag, &[], ancestor_tags, parent_style);
let mut new_ancestors = ancestor_tags.to_vec();
new_ancestors.push(tag.to_string());
let children: Vec<Node> = del
.children
.iter()
.map(|child| build_node(child, resolver, &new_ancestors, &style))
.collect();
Node::new(
NodeKind::Delete { children },
style,
true,
)
}
mdast::Node::Html(_) => Node::new(
NodeKind::Text {
text: String::new(),
},
Style::default(),
false,
),
_ => Node::new(
NodeKind::Text {
text: String::new(),
},
Style::default(),
true,
),
}
}
fn build_inline_children(
children: &[mdast::Node],
resolver: &StyleResolver,
ancestor_tags: &[String],
parent_style: &Style,
) -> Vec<Node> {
children
.iter()
.map(|child| build_node(child, resolver, ancestor_tags, parent_style))
.collect()
}
pub fn walk<F>(node: &Node, callback: &mut F)
where
F: FnMut(&Node),
{
callback(node);
match &node.kind {
NodeKind::Document { children }
| NodeKind::Heading { children, .. }
| NodeKind::Paragraph { children }
| NodeKind::List { children, .. }
| NodeKind::ListItem { children }
| NodeKind::Blockquote { children }
| NodeKind::Table { children, .. }
| NodeKind::TableRow { children }
| NodeKind::Strong { children }
| NodeKind::Emphasis { children }
| NodeKind::Link { children, .. }
| NodeKind::Delete { children } => {
for child in children {
walk(child, callback);
}
}
_ => {}
}
}
pub fn collect_text(node: &Node) -> String {
let mut result = String::new();
walk(node, &mut |n| {
if let NodeKind::Text { text } = &n.kind {
result.push_str(text);
}
});
result
}