Current section

Files

Jump to
rbt_weave crates weave-content src html escape.rs
Raw

crates/weave-content/src/html/escape.rs

//! HTML escaping, truncation, and inline markdown rendering.
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
pub(crate) fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
let truncated: String = s.chars().take(max.saturating_sub(3)).collect();
format!("{truncated}...")
}
}
pub(crate) fn escape(s: &str) -> String {
s.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
}
pub(crate) fn escape_attr(s: &str) -> String {
escape(s)
}
/// Convert a `snake_case` enum value to Title Case for display.
/// Custom values prefixed with `custom:` are returned as-is.
pub(crate) fn format_enum(s: &str) -> String {
if let Some(custom) = s.strip_prefix("custom:") {
return custom.to_string();
}
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(c) => {
let upper: String = c.to_uppercase().collect();
upper + chars.as_str()
}
}
})
.collect::<Vec<_>>()
.join(" ")
}
/// Render inline markdown to HTML, stripping block-level wrappers.
///
/// Supports bold, italic, links, and inline code. Block-level elements
/// (paragraphs, headings, lists) are flattened to their inline content.
pub(crate) fn render_inline_markdown(s: &str) -> String {
let parser = Parser::new_ext(s, Options::empty());
let mut out = String::with_capacity(s.len());
for event in parser {
match event {
Event::Text(text) => out.push_str(&escape(&text)),
Event::Code(code) => {
out.push_str("<code>");
out.push_str(&escape(&code));
out.push_str("</code>");
}
Event::Start(Tag::Strong) => out.push_str("<strong>"),
Event::End(TagEnd::Strong) => out.push_str("</strong>"),
Event::Start(Tag::Emphasis) => out.push_str("<em>"),
Event::End(TagEnd::Emphasis) => out.push_str("</em>"),
Event::Start(Tag::Link { dest_url, .. }) => {
out.push_str(&format!(
"<a href=\"{}\" rel=\"noopener noreferrer\">",
escape_attr(&dest_url)
));
}
Event::End(TagEnd::Link) => out.push_str("</a>"),
Event::SoftBreak | Event::HardBreak => out.push(' '),
_ => {}
}
}
out
}