pub mod block_decorations;
pub mod code_block;
pub mod conflict_resolver;
pub mod emphasis;
pub mod inline_decorations;
pub mod media;
pub mod nested_blocks;
pub mod plugin_markers;
pub mod plugins;
pub mod preprocessor;
pub mod table;
pub fn apply_extensions(html: &str) -> String {
let header_map = conflict_resolver::HeaderIdMap::new();
let options = crate::parser::ParserOptions::default();
apply_extensions_with_headers(html, &header_map, &options)
}
pub fn apply_extensions_with_headers(
html: &str,
header_map: &conflict_resolver::HeaderIdMap,
options: &crate::parser::ParserOptions,
) -> String {
let mut result = html.to_string();
let (protected, placeholders) = protect_code_sections(&result);
result = protected;
result = media::transform_images_to_media(&result);
result = conflict_resolver::postprocess_conflicts(&result, header_map);
result = emphasis::apply_umd_emphasis(&result);
result = block_decorations::apply_block_placement(&result); result = block_decorations::apply_block_decorations(&result);
result = inline_decorations::apply_inline_decorations(&result);
if let Some(base_url) = &options.base_url {
result = conflict_resolver::apply_base_url_to_links(&result, base_url);
}
restore_code_sections(&result, &placeholders)
}
fn protect_code_sections(html: &str) -> (String, Vec<String>) {
use regex::Regex;
let mut placeholders = Vec::new();
let mut result = html.to_string();
let code_block_re = Regex::new(r"<pre><code[^>]*>[\s\S]*?</code></pre>").unwrap();
result = code_block_re
.replace_all(&result, |caps: ®ex::Captures| {
let index = placeholders.len();
placeholders.push(caps[0].to_string());
format!("<!--CODE_BLOCK_{}-->", index)
})
.to_string();
let inline_code_re = Regex::new(r"<code[^>]*>[^<]*</code>").unwrap();
result = inline_code_re
.replace_all(&result, |caps: ®ex::Captures| {
let index = placeholders.len();
placeholders.push(caps[0].to_string());
format!("<!--INLINE_CODE_{}-->", index)
})
.to_string();
(result, placeholders)
}
fn restore_code_sections(html: &str, placeholders: &[String]) -> String {
use regex::Regex;
let mut result = html.to_string();
let placeholder_re = Regex::new(r"<!--(CODE_BLOCK|INLINE_CODE)_(\d+)-->").unwrap();
result = placeholder_re
.replace_all(&result, |caps: ®ex::Captures| {
let section_type = caps.get(1).map(|m| m.as_str()).unwrap_or("");
let index: usize = caps[2].parse().unwrap();
let original = placeholders.get(index).map(|s| s.as_str()).unwrap_or("");
if section_type == "INLINE_CODE" {
enhance_inline_code_color_sample(original)
} else {
original.to_string()
}
})
.to_string();
result = code_block::process_code_blocks(&result);
result
}
fn enhance_inline_code_color_sample(inline_code_html: &str) -> String {
use once_cell::sync::Lazy;
use regex::Regex;
static INLINE_CODE_TAG_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"^<code(?P<attrs>[^>]*)>(?P<content>[^<]*)</code>$"#)
.expect("valid inline code tag regex")
});
static HEX_COLOR_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$")
.expect("valid hex color regex")
});
static RGB_COLOR_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)^rgb\(\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*\)$",
)
.expect("valid rgb color regex")
});
static RGBA_COLOR_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)^rgba\(\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d?\d)\s*,\s*(?:0|1|0?\.\d+|1(?:\.0+)?)\s*\)$",
)
.expect("valid rgba color regex")
});
static HSL_COLOR_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)^hsl\(\s*(?:360|3[0-5]\d|[12]?\d?\d)\s*,\s*(?:100|[1-9]?\d)%\s*,\s*(?:100|[1-9]?\d)%\s*\)$",
)
.expect("valid hsl color regex")
});
static HSLA_COLOR_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)^hsla\(\s*(?:360|3[0-5]\d|[12]?\d?\d)\s*,\s*(?:100|[1-9]?\d)%\s*,\s*(?:100|[1-9]?\d)%\s*,\s*(?:0|1|0?\.\d+|1(?:\.0+)?)\s*\)$",
)
.expect("valid hsla color regex")
});
let Some(caps) = INLINE_CODE_TAG_RE.captures(inline_code_html) else {
return inline_code_html.to_string();
};
let attrs = caps.name("attrs").map(|m| m.as_str()).unwrap_or("");
let content = caps.name("content").map(|m| m.as_str()).unwrap_or("");
let color_text = content.trim();
let is_color_code = HEX_COLOR_RE.is_match(color_text)
|| RGB_COLOR_RE.is_match(color_text)
|| RGBA_COLOR_RE.is_match(color_text)
|| HSL_COLOR_RE.is_match(color_text)
|| HSLA_COLOR_RE.is_match(color_text);
if !is_color_code {
return inline_code_html.to_string();
}
format!(
r#"<code{}>{}<span class="inline-code-color" style="background-color: {};"></span></code>"#,
attrs, content, color_text
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_umd_syntax_integration() {
let input = "<p>This is ''bold'' and '''italic'''</p>";
let output = apply_extensions(input);
assert!(output.contains("<b>bold</b>"));
assert!(output.contains("<i>italic</i>"));
}
}