pub fn normalize(text: &str) -> String {
let lower = text.to_ascii_lowercase();
let smart_quoted = lower
.replace(['\u{2018}', '\u{2019}'], "'")
.replace(['\u{201C}', '\u{201D}'], "\"");
let mut out = String::with_capacity(smart_quoted.len());
for ch in smart_quoted.chars() {
let keep = ch.is_ascii_alphanumeric() || ch.is_ascii_whitespace() || ch == '\'';
if keep {
out.push(ch);
} else {
out.push(' ');
}
}
out.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::normalize;
#[test]
fn normalize_lowercases_ascii() {
assert_eq!(normalize("HELLO World"), "hello world");
}
#[test]
fn normalize_strips_punctuation() {
assert_eq!(normalize("Hello, world!"), "hello world");
}
#[test]
fn normalize_keeps_apostrophes() {
assert_eq!(normalize("don't stop"), "don't stop");
}
#[test]
fn normalize_smart_quotes_to_ascii() {
assert_eq!(
normalize("\u{2018}hello\u{2019} world\u{201D}"),
"'hello' world"
);
}
#[test]
fn normalize_collapses_whitespace() {
assert_eq!(normalize(" hello\t\n world "), "hello world");
}
#[test]
fn normalize_pure_punctuation_yields_empty() {
assert_eq!(normalize("…!?"), "");
}
}