use std::borrow::Cow;
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;
#[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"
);
}
}