pub mod css;
pub mod node;
pub mod presets;
pub mod style;
pub use css::*;
pub use node::*;
pub use presets::*;
pub use style::*;
use markdown::mdast;
pub fn parse_markdown(markdown: &str) -> Result<Node, String> {
let (node, _page_config) = parse_markdown_with_css(markdown, "")?;
Ok(node)
}
pub fn parse_markdown_with_css(
markdown: &str,
user_css: &str,
) -> Result<(Node, PageConfig), String> {
let mdast = markdown::to_mdast(markdown, &markdown::ParseOptions::gfm()).unwrap_or_else(|_| {
mdast::Node::Root(mdast::Root {
children: vec![],
position: None,
})
});
let style_css = extract_style_css(&mdast);
let combined_css = if user_css.is_empty() {
style_css
} else if style_css.is_empty() {
user_css.to_string()
} else {
format!("{}\n{}", user_css, style_css)
};
let resolver = StyleResolver::new(DEFAULT_CSS)?
.with_strict_mode(false)
.with_user_css(&combined_css)?;
let node = node::build_ast(&mdast, &resolver);
let page_config = resolver.page_config().clone();
Ok((node, page_config))
}
pub fn parse_markdown_with_css_strict(
markdown: &str,
user_css: &str,
) -> Result<(Node, PageConfig), String> {
let mdast = markdown::to_mdast(markdown, &markdown::ParseOptions::gfm()).unwrap_or_else(|_| {
mdast::Node::Root(mdast::Root {
children: vec![],
position: None,
})
});
let style_css = extract_style_css(&mdast);
let combined_css = if user_css.is_empty() {
style_css
} else if style_css.is_empty() {
user_css.to_string()
} else {
format!("{}\n{}", user_css, style_css)
};
let resolver = StyleResolver::new(DEFAULT_CSS)?
.with_strict_mode(true)
.with_user_css(&combined_css)?;
let node = node::build_ast(&mdast, &resolver);
let page_config = resolver.page_config().clone();
Ok((node, page_config))
}
pub fn parse_markdown_with_resolver(markdown: &str, resolver: &StyleResolver) -> Node {
let mdast = markdown::to_mdast(markdown, &markdown::ParseOptions::gfm()).unwrap_or_else(|_| {
mdast::Node::Root(mdast::Root {
children: vec![],
position: None,
})
});
node::build_ast(&mdast, resolver)
}
fn extract_style_css(node: &mdast::Node) -> String {
let mut css_parts = Vec::new();
collect_style_css(node, &mut css_parts);
css_parts.join("\n")
}
fn collect_style_css(node: &mdast::Node, css_parts: &mut Vec<String>) {
if let mdast::Node::Html(html) = node
&& let Some(css) = extract_style_text(&html.value)
{
css_parts.push(css);
}
if let Some(children) = mdast_children(node) {
for child in children {
collect_style_css(child, css_parts);
}
}
}
fn extract_style_text(html: &str) -> Option<String> {
let html = html.trim();
let tag_start = html.find("<style")?;
let after_tag = &html[tag_start..];
let bracket_end = after_tag.find('>')?;
let content_start = tag_start + bracket_end + 1;
let remaining = &html[content_start..];
let close_end = remaining.find("</style>")?;
let css = remaining[..close_end].trim();
if css.is_empty() {
None
} else {
Some(css.to_string())
}
}
fn mdast_children(node: &mdast::Node) -> Option<&[mdast::Node]> {
match node {
mdast::Node::Root(n) => Some(&n.children),
mdast::Node::Paragraph(n) => Some(&n.children),
mdast::Node::Heading(n) => Some(&n.children),
mdast::Node::List(n) => Some(&n.children),
mdast::Node::ListItem(n) => Some(&n.children),
mdast::Node::Blockquote(n) => Some(&n.children),
mdast::Node::Table(n) => Some(&n.children),
mdast::Node::TableRow(n) => Some(&n.children),
mdast::Node::TableCell(n) => Some(&n.children),
mdast::Node::Strong(n) => Some(&n.children),
mdast::Node::Emphasis(n) => Some(&n.children),
mdast::Node::Link(n) => Some(&n.children),
mdast::Node::Delete(n) => Some(&n.children),
_ => None,
}
}