use crate::grapesjs::GrapesJsTheme;
fn theme_head_html(theme_id: &str, theme: &GrapesJsTheme) -> String {
if theme_id.is_empty() {
return String::new();
}
let escaped_id = html_escape(theme_id);
let mut b = String::new();
for src in &theme.scripts {
let src = src.trim();
if src.is_empty() {
continue;
}
b.push_str("<script src=\"");
b.push_str(&html_escape(src));
b.push_str("\" data-lariv-theme=\"");
b.push_str(&escaped_id);
b.push_str("\"></script>\n");
}
for href in &theme.stylesheets {
let href = href.trim();
if href.is_empty() {
continue;
}
b.push_str("<link rel=\"stylesheet\" href=\"");
b.push_str(&html_escape(href));
b.push_str("\" data-lariv-theme=\"");
b.push_str(&escaped_id);
b.push_str("\">\n");
}
let css = theme.css.trim();
if !css.is_empty() {
b.push_str("<style");
if let Some(css_type) = theme.css_type.as_deref().filter(|s| !s.is_empty()) {
b.push_str(" type=\"");
b.push_str(&html_escape(css_type));
b.push_str("\"");
}
b.push_str(" data-lariv-theme=\"");
b.push_str(&escaped_id);
b.push_str("\">\n");
b.push_str(css);
b.push_str("\n</style>\n");
}
b
}
fn theme_body_js_html(theme_id: &str, theme: &GrapesJsTheme) -> String {
if theme_id.is_empty() {
return String::new();
}
let js = theme.js.trim();
if js.is_empty() {
return String::new();
}
let mut b = String::new();
b.push_str("<script data-lariv-theme=\"");
b.push_str(&html_escape(theme_id));
b.push_str("\">\n");
b.push_str(js);
b.push_str("\n</script>\n");
b
}
fn html_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
fn strip_lariv_theme_assets(html_doc: &str) -> String {
let mut out = html_doc.to_string();
loop {
let lower = out.to_lowercase();
if let Some((start, end)) =
find_tagged_block(&lower, "<style", "data-lariv-theme", "</style>")
{
out.replace_range(start..end, "");
continue;
}
if let Some((start, end)) = find_link_tag(&lower, "data-lariv-theme") {
out.replace_range(start..end, "");
continue;
}
if let Some((start, end)) = find_script_tag(&lower, "data-lariv-theme") {
out.replace_range(start..end, "");
continue;
}
break;
}
out
}
fn find_tagged_block(lower: &str, open: &str, marker: &str, close: &str) -> Option<(usize, usize)> {
let mut search = 0;
while let Some(rel) = lower[search..].find(open) {
let start = search + rel;
let after_open = start + open.len();
let gt = lower[after_open..].find('>')?;
let tag_end = after_open + gt + 1;
if lower[start..tag_end].contains(marker)
&& let Some(crel) = lower[tag_end..].find(close)
{
let mut end = tag_end + crel + close.len();
if lower.as_bytes().get(end) == Some(&b'\n') {
end += 1;
}
return Some((start, end));
}
search = tag_end;
}
None
}
fn find_script_tag(lower: &str, marker: &str) -> Option<(usize, usize)> {
let mut search = 0;
while let Some(rel) = lower[search..].find("<script") {
let start = search + rel;
let after_open = start + 7;
let close = lower[after_open..].find("</script>")?;
let mut end = after_open + close + 9;
let tag_end = lower[after_open..]
.find('>')
.map(|gt| after_open + gt + 1)?;
if lower[start..tag_end.min(end)].contains(marker) {
if lower.as_bytes().get(end) == Some(&b'\n') {
end += 1;
}
return Some((start, end));
}
search = end;
}
None
}
fn find_link_tag(lower: &str, marker: &str) -> Option<(usize, usize)> {
let mut search = 0;
while let Some(rel) = lower[search..].find("<link") {
let start = search + rel;
let after_open = start + 5;
let gt = lower[after_open..].find('>')?;
let mut end = after_open + gt + 1;
if lower[start..end].contains(marker) {
if lower.as_bytes().get(end) == Some(&b'\n') {
end += 1;
}
return Some((start, end));
}
search = end;
}
None
}
pub fn inject_theme_assets(
html_doc: &str,
theme_id: &str,
theme: Option<&GrapesJsTheme>,
) -> String {
let html_doc = strip_lariv_theme_assets(html_doc);
let Some(theme) = theme else {
return html_doc;
};
let head_block = theme_head_html(theme_id, theme);
let body_js = theme_body_js_html(theme_id, theme);
if head_block.is_empty() && body_js.is_empty() {
return html_doc;
}
let lower = html_doc.to_lowercase();
let mut out = if !head_block.is_empty() {
if let Some(idx) = lower.find("</head>") {
format!("{}{}{}", &html_doc[..idx], head_block, &html_doc[idx..])
} else if let Some(idx) = lower.find("<body") {
format!(
"{}<head>\n{}</head>\n{}",
&html_doc[..idx],
head_block,
&html_doc[idx..]
)
} else if lower.contains("<html") {
format!("{head_block}{html_doc}")
} else {
format!(
"<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n{head_block}</head>\n<body>\n{html_doc}\n</body>\n</html>\n"
)
}
} else {
html_doc
};
if !body_js.is_empty() {
let lower = out.to_lowercase();
if let Some(idx) = lower.rfind("</body>") {
out = format!("{}{}{}", &out[..idx], body_js, &out[idx..]);
} else {
out.push_str(&body_js);
}
}
out
}