use crate::segment::Segment;
use crate::terminal_theme::TerminalTheme;
const CONSOLE_HTML_FORMAT: &str = r#"<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
{stylesheet}
body {
color: {foreground};
background-color: {background};
}
</style>
</head>
<body>
<pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><code style="font-family:inherit">{code}</code></pre>
</body>
</html>
"#;
fn escape(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
fn fill_template(code: &str, stylesheet: &str, theme: &TerminalTheme) -> String {
CONSOLE_HTML_FORMAT
.replace("{stylesheet}", stylesheet)
.replace("{foreground}", &theme.foreground.hex())
.replace("{background}", &theme.background.hex())
.replace("{code}", code)
}
pub fn export_html_inline(segments: &[Segment], theme: &TerminalTheme) -> String {
let simplified = Segment::simplify(segments);
let mut code = String::new();
for segment in &simplified {
if segment.control {
continue;
}
let text = escape(&segment.text);
match &segment.style {
Some(style) if !style.is_null() => {
let rule = style.get_html_style(theme);
if rule.is_empty() {
code.push_str(&text);
} else {
code.push_str(&format!("<span style=\"{rule}\">{text}</span>"));
}
}
_ => code.push_str(&text),
}
}
fill_template(&code, "", theme)
}
pub fn export_html_classes(segments: &[Segment], theme: &TerminalTheme) -> String {
let simplified = Segment::simplify(segments);
let mut styles: Vec<(String, usize)> = Vec::new();
let mut code = String::new();
for segment in &simplified {
if segment.control {
continue;
}
let text = escape(&segment.text);
match &segment.style {
Some(style) if !style.is_null() => {
let rule = style.get_html_style(theme);
if rule.is_empty() {
code.push_str(&text);
} else {
let number = match styles.iter().find(|(existing, _)| *existing == rule) {
Some((_, n)) => *n,
None => {
let n = styles.len() + 1;
styles.push((rule, n));
n
}
};
code.push_str(&format!("<span class=\"r{number}\">{text}</span>"));
}
}
_ => code.push_str(&text),
}
}
let stylesheet = styles
.iter()
.map(|(rule, number)| format!(".r{number} {{{rule}}}"))
.collect::<Vec<_>>()
.join("\n");
fill_template(&code, &stylesheet, theme)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Style;
#[test]
fn escapes_html_special_chars() {
assert_eq!(escape("a<b>&\"'c"), "a<b>&"'c");
}
#[test]
fn bold_red_html_style() {
let style = Style::parse("bold red").unwrap();
assert_eq!(
style.get_html_style(&crate::terminal_theme::DEFAULT_TERMINAL_THEME),
"color: #800000; text-decoration-color: #800000; font-weight: bold"
);
}
}