use std::borrow::Cow;
pub(crate) fn decode_mermaid_entities_for_render_text(text: &str) -> Cow<'_, str> {
if !text.contains('\u{fb02}') && !text.contains('\u{00b6}') && !text.contains('#') {
return Cow::Borrowed(text);
}
merman_core::entities::decode_mermaid_entities_to_unicode(text)
}
pub(crate) fn decode_svg_text_content_entities(text: &str) -> Cow<'_, str> {
if !text.contains("<") && !text.contains(">") && !text.contains("&") {
return Cow::Borrowed(text);
}
Cow::Owned(decode_stage1_lt_gt_amp(text))
}
pub(crate) fn decode_entities_minimal(text: &str) -> String {
if !text.contains('&') {
return text.to_string();
}
let stage1 = decode_stage1_lt_gt_amp(text);
if !stage1.contains('&') {
return stage1;
}
if !stage1.contains(""") && !stage1.contains("'") {
return stage1;
}
decode_stage2_quot_apos(&stage1)
}
pub(crate) fn decode_entities_minimal_cow(text: &str) -> Cow<'_, str> {
if !text.contains('&') {
return Cow::Borrowed(text);
}
Cow::Owned(decode_entities_minimal(text))
}
fn decode_stage1_lt_gt_amp(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(pos) = rest.find('&') {
out.push_str(&rest[..pos]);
let tail = &rest[pos..];
if let Some(stripped) = tail.strip_prefix("<") {
out.push('<');
rest = stripped;
} else if let Some(stripped) = tail.strip_prefix(">") {
out.push('>');
rest = stripped;
} else if let Some(stripped) = tail.strip_prefix("&") {
out.push('&');
rest = stripped;
} else {
out.push('&');
rest = &tail[1..];
}
}
out.push_str(rest);
out
}
fn decode_stage2_quot_apos(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(pos) = rest.find('&') {
out.push_str(&rest[..pos]);
let tail = &rest[pos..];
if let Some(stripped) = tail.strip_prefix(""") {
out.push('"');
rest = stripped;
} else if let Some(stripped) = tail.strip_prefix("'") {
out.push('\'');
rest = stripped;
} else {
out.push('&');
rest = &tail[1..];
}
}
out.push_str(rest);
out
}
#[cfg(test)]
mod tests {
use super::{
decode_entities_minimal, decode_mermaid_entities_for_render_text,
decode_svg_text_content_entities,
};
#[test]
fn mermaid_placeholders_share_one_render_text_decoder() {
assert_eq!(
decode_mermaid_entities_for_render_text(
"left \u{fb02}\u{00b0}lt\u{00b6}\u{00df} right"
),
"left < right"
);
}
#[test]
fn decode_entities_minimal_direct_entities() {
assert_eq!(decode_entities_minimal("<"), "<");
assert_eq!(decode_entities_minimal(">"), ">");
assert_eq!(decode_entities_minimal("&"), "&");
assert_eq!(decode_entities_minimal("""), "\"");
assert_eq!(decode_entities_minimal("'"), "'");
}
#[test]
fn decode_entities_minimal_preserves_unknown_entities() {
assert_eq!(decode_entities_minimal("&unknown;"), "&unknown;");
assert_eq!(decode_entities_minimal("a&b"), "a&b");
}
#[test]
fn decode_entities_minimal_order_matters_like_replace_chain() {
assert_eq!(decode_entities_minimal("&quot;"), "\"");
assert_eq!(decode_entities_minimal("&#39;"), "'");
assert_eq!(decode_entities_minimal("&lt;"), "<");
assert_eq!(decode_entities_minimal("&gt;"), ">");
}
#[test]
fn decode_entities_minimal_mixed_text() {
assert_eq!(
decode_entities_minimal("a < b && b > c"),
"a < b && b > c"
);
}
#[test]
fn svg_text_content_decodes_only_sanitizer_entities() {
assert_eq!(
decode_svg_text_content_entities("& < >   " &unknown;"),
"& < >   " &unknown;"
);
}
}